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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With