Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert type family instances to Int

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
like image 608
mentics Avatar asked Feb 21 '23 04:02

mentics


2 Answers

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.

like image 116
ehird Avatar answered Feb 23 '23 17:02

ehird


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.

like image 25
Heatsink Avatar answered Feb 23 '23 17:02

Heatsink