Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create instances for phantom types returning phantom type?

Let's have the following data types:

data Foo1 a = Foo1
data Foo2 a = Foo2 (Foo3 a)
data Foo3 a = C1 (Foo1 a) | C2 Int

And now we want to be able to get a Foo3 from a Foo1 or an Int. A solution could be to use a type class:

class ToFoo3 a where
    toFoo3 :: a -> Foo3 b -- Here start the problems with this phantom type b...

instance ToFoo3 (Foo1 b) where
    toFoo3 foo1 = C1 foo1

instance ToFoo3 Int where
    toFoo3 int = C2 int

And here the compiler complains (rightly!) that it cannot match b with b1 because the "b" of Foo3 in the class definition is not the same as the one of Foo1 in the instance.

Is there a way to solve this?

like image 516
leo Avatar asked Dec 04 '22 05:12

leo


1 Answers

A multiparameter typeclass without a functional dependency compiles for me:

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}

data Foo1 a = Foo1
data Foo2 a = Foo2 (Foo3 a)
data Foo3 a = C1 (Foo1 a) | C2 Int

class ToFoo3 a b where
    toFoo3 :: a -> Foo3 b

instance ToFoo3 (Foo1 b) b where
    toFoo3 foo1 = C1 foo1

instance ToFoo3 Int b where
    toFoo3 int = C2 int

The way I understand it, you cannot have a functional dependency in either direction, since an Int needs to be able to convert to any Foo3 a type, and Foo1 a also needs to be able to convert to the same Foo3 a type.

Of course this means you cannot expect any of the argument or result type of toFoo3 to help with infering the other, so you might sometimes need an annoying amount of type annotations to use this, but other than that this should work.

EDIT: I am assuming you don't want to be able to convert from Foo1 a to Foo3 b with a and b different. If I am wrong about that, then your OP code with a single-parameter class should work if you change one instance to

instance ToFoo3 (Foo1 b) where
    toFoo3 Foo1 = C1 Foo1
like image 105
Ørjan Johansen Avatar answered Dec 25 '22 22:12

Ørjan Johansen