Type Family Type Hackery
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 Format s, which define a number of Target s. 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. Formatter s 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:
DateFormat specifies targets like Year , Month , and Day . MonthType is a Formattable newtype of Int that has names such as "February" instance Formattable Int where name = show DateTime might 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 Formattable s 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 f does not determine f , which means that the function
target :: (Formatter f x, Formatable y) => Target f -> x -> y
can never be called. No matter what type annotation you give to target , you can't nail down what f is, and so the compiler can never figure out which Formatter instance to use. I'm not 100% sure, but probably the solution is not to use multi-parameter type classes and to let one of x or f be a function of the other. Also, you should probably just delete the Format class entirely (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
链接地址: http://www.djcxy.com/p/78244.html
上一篇: 'type family'vs'data family',简而言之?
下一篇: 类型家庭类型的黑客
