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',简而言之?
下一篇: 类型家庭类型的黑客