Convert type family instances to Int
-
14-12-2019 - |
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
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 a
s and Id a
s? 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.