Question

I have this code:

type family Id obj :: *
type instance Id Box = Int

And I want to make it so I can always get an Int from the Id type family. I recognize that a conversion will be required.

I thought maybe creating a class would work:

class IdToInt a where
  idToInt :: Id a -> Int

instance IdToInt Box where
  idToInt s = s

And that actually compiles. But when I try to use it:

testFunc :: Id a -> Int
testFunc x = idToInt x

I get error:

src/Snowfall/Spatial.hs:29:22:
Couldn't match type `Id a0' with `Id a'
NB: `Id' is a type function, and may not be injective
In the first argument of `idToInt', namely `x'
In the expression: idToInt x
In an equation for `testFunc': testFunc x = idToInt x

So, how can I create a conversion for a type family Id to get an Int?

Based on the answer by ehird, I tried the following but it doesn't work either:

class IdStuff a where
  type Id a :: *
  idToInt :: Id a -> Int

instance IdStuff Box where
  type Id Box = Int
  idToInt s = s

testFunc :: (IdStuff a) => Id a -> Int
testFunc x = idToInt x

It gives error:

src/Snowfall/Spatial.hs:45:22:
Could not deduce (Id a0 ~ Id a)
from the context (IdStuff a)
  bound by the type signature for
             testFunc :: IdStuff a => Id a -> Int
  at src/Snowfall/Spatial.hs:45:1-22
NB: `Id' is a type function, and may not be injective
In the first argument of `idToInt', namely `x'
In the expression: idToInt x
In an equation for `testFunc': testFunc x = idToInt x
Was it helpful?

Solution

As others have pointed out, the problem is that the compiler can't figure out which a to use. Data families are one solution, but an alternative that's sometimes easier to work with is to use a type witness.

Change your class to

class IdToInt a where
  idToInt :: a -> Id a -> Int

instance IdToInt Box where
  idToInt _ s = s

-- if you use this a lot, it's sometimes useful to create type witnesses to use
box = undefined :: Box

-- you can use it like
idToInt box someId

-- or
idToInt someBox (getId someBox)

The question you need to answer is, for any given Id, is there only one type a it should appear with? That is, is there a one to one correspondence between as and Id as? If so, data families are the correct approach. If not, you may prefer a witness.

OTHER TIPS

You can't. You'll need testFunc :: (IdToInt a) => Id a -> Int. Type families are open, so anyone can declare

type instance Id Blah = ()

at any time, and offer no conversion function. The best thing to do is to put the type family in the class:

class HasId a where
  type Id a
  idToInt :: Id a -> Int

instance IdToInt Box where
  type Id Box = Int
  idToInt s = s

You'll still need the context, though.

You cannot use a function of type IdToInt a => Id a -> Int because there is no way to determine what type a is. The following example demonstrates this.

type family Id a :: *
type instance Id () = Int
type instance Id Char = Int

class IdToInt a where idToInt :: Id a -> Int

instance IdToInt () where idToInt x = x + 1
instance IdToInt Char where idToInt x = x - 1

main = print $ idToInt 1

Because Id () = Id Char = Int, the type of idToInt in the above context is Int -> Int, which is equal to Id () -> Int and Id Char -> Int. Remember that overloaded methods are chosen based on type. Both class instances define idToInt functions that have type Int -> Int, so the type checker cannot decide which one to use.

You should use a data family instead of a type family, and declare newtype instances.

data family Id a :: *
newtype instance Id () = IdUnit Int
newtype instance Id Char = IdChar Int

With a newtype instance, Id () and Id Char are both ints, but they have different types. The type of an Id informs the type checker which overloaded function to use.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top