I’m trying to write a fairly polymorphic library. I’ve run into a situation that’s easier to show than tell. It looks a bit 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 Formats, which define a number of Targets. There are also a number of Formattable objects, which know how to respond to a bunch of different format options (reduced here to simply name).
These Formattables are composed in a variety of ways, and can respond to a number of different targets. Formatters are essentially the router between Format and Formattable — given a target (from a specific format) they respond with a suitable Formattable object.
This is all pretty abstract. Here’s an example:
DateFormatspecifies targets likeYear,Month, andDay.MonthTypeis aFormattablenewtype ofIntthat has names such as “February”- There is also the simple
instance Formattable Int where name = show DateTimemight be a type synonym for(Int, MonthType, Int).
(Obviously, I’ve cut out a lot of machinery here, such as piping the correct values around, but you get the idea.)
The display function is fairly simple. It takes a formatter, a string specifying the format, an object to display, and renders it all into a string.
First it breaks the string up into targets and strings. For example, a date formatter might break the string "%Y-%m-%d" into [Right Year, Left "-", Right Month, Left "-", Right Day]. The split function does that, and has been redacted here.
The display function simply tracks down the Formattables for each target and accumulates the string.
Or, at least, it`s supposed to.
But it fails typechecking 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 can’t for the life of me figure out why. What am I doing wrong?
The problem is that
Target fdoes not determinef, which means that the functioncan never be called. No matter what type annotation you give to
target, you can’t nail down whatfis, and so the compiler can never figure out whichFormatterinstance to use. I’m not 100% sure, but probably the solution is not to use multi-parameter type classes and to let one ofxorfbe a function of the other. Also, you should probably just delete theFormatclass entirely (did you know you don’t need a class to use a type family?). Perhaps something like this: