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 as Year

    , Month

    and Day

    .
  • MonthType

    is a Formattable

    newtype Int

    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?

+3


source to share


1 answer


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

      

+2


source







All Articles