Type Family Type Hacker
I am trying to write a fairly polymorphic library. I am faced with a situation that is easier to show than to tell. It looks something like this:
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
import Data.Map (Map)
import qualified Data.Map as Map
class Format f where type Target f
class Format f => Formatter x f where
target :: forall y. Formatable y => Target f -> x -> y
class Formatable y where name :: y -> String
instance Formatable Integer where name = show
instance Formatable Int where name = show
split :: forall x f. (Format f, Formatter x f) => x -> f -> String -> [Either String (Target f)]
split = undefined
display :: forall x f. (Format f, Formatter x f) => f -> String -> x -> String
display f str x = let
chunks = split x f str
built = foldr apply "" chunks
apply (Left s) accum = accum ++ s
apply (Right t) accum = accum ++ name (target t x)
in foldr apply "" chunks
Essentially, we have polymorphic Format
s that define the number Target
s. There are also a few entities Formattable
that know how to respond to a bunch of different format parameters (abbreviated here as simple name
).
These are Formattables
drafted in a variety of ways and can respond to several different goals. Formatter
- it is essentially a router between Format
and Formattable
- given the purpose (from a specific format) they respond with a suitable entity Formattable
.
This is all pretty abstract. Here's an example:
-
DateFormat
indicates goals such asYear
,Month
andDay
. -
MonthType
is aFormattable
newtypeInt
that has names like "February" - There is also a simple
instance Formattable Int where name = show
-
DateTime
can be a synonym for type(Int, MonthType, Int)
.
(Obviously I've carved out a lot of gears here, like piping the correct values around, but you get the idea.)
The function is display
pretty simple. It takes a formatter, a format string, an object to display, and turns it all into a string.
It first splits the string into targets and lines. For example, date formatting might split the string "%Y-%m-%d"
into [Right Year, Left "-", Right Month, Left "-", Right Day]
. The function split
does this and has been edited here.
The function display
just tracks Formattable
for each target and accumulates the string.
Or at least it should be.
But it fails type checking with the following error:
Reduced.hs:20:16:
Could not deduce (Target f ~ Target f0)
from the context (Format f, Formatter x f)
bound by the type signature for
display :: (Format f, Formatter x f) => f -> String -> x -> String
at Reduced.hs:(19,5)-(24,30)
NB: `Target' is a type function, and may not be injective
Expected type: [Either [Char] (Target f0)]
Actual type: [Either String (Target f)]
In the return type of a call of `split'
In the expression: split x f str
In an equation for `chunks': chunks = split x f str
Failed, modules loaded: none.
and I cannot understand for life why. What am I doing wrong?
source to share
The problem is it Target f
doesn't define f
what the function means
target :: (Formatter f x, Formatable y) => Target f -> x -> y
can never be called. No matter what type annotation you point to target
, you cannot nail what f
, and therefore the compiler cannot figure out which instance to Formatter
use. I'm not 100% sure, but the solution is probably not to use classes with multiple parameters and let one of x
or f
be a function of the other. Also, you should just simply remove the class Format
(did you know you don't need a class to use a type family?). Perhaps something like this:
class Formatter x where
type Format x
target :: Formatable y => Format x -> x -> y
source to share