When I use MultiParamTypeClasses, I can create class functions that ignore one of the type parameters (ie- like "identity" below).
{-# LANGUAGE MultiParamTypeClasses #-}
data Add = Add
data Mul = Mul
class Test a b where
identity::a
instance Test Int Add where
identity = 0
instance Test Int Mul where
identity = 1
(this is a stripped down version, of course in the full program there would be other functions that would use "b").
The example compiles, but I can never access identity!
main = do
putStrLn (show (identity::Int))
leads to "No instance for (Test Int b0) arising from a use of 'identity'.
Is there a way to access identity? If not, shouldn't the compiler forbid me from ever creating a class function that doesn't use all type parameters?
If not, shouldn't the compiler forbid me from ever creating a class function that doesn't use all type parameters?
Perhaps. Indeed you'll never be able to use such a class method. But as the error occurs always at compile-time, it's not really dangerous.
Fixes that work in some similar cases (not in yours, though):
Make the undetermined type variable functionally dependent on one of the others.
{-# LANGUAGE FunctionalDependencies #-}
class Group_FD g p | g->p where
identity :: g
This could be used perhaps like
data Nat = One | Succ Nat
instance Group_FD Nat Mult where
identity = One
instance Group_FD Int Add where
identity = 0
But it's obviously not possible to make multiple instances with the same g
element this way.
Define a seperate class with only one parameter for the methods that depend only on that. Then make this class a constraint ("superclass") on the other one, to "import" methods:
class Identity i where
identity :: i
class (Identity i) => Test i y
that's no use at all for your application though, since you want the behaviour of identity
to depend on the Phantom type variable.
To achieve your goal, you must somehow pass the information of which instance you want. One way to achieve this is phantom arguments:
class Group_PA g p where
identity :: p -> g
instance Group_PA Int Add where
identity _ = 0
instance Group_PA Int Mult where
identity _ = 1
You could then use that like
GHCi> identity Add :: Int
0
GHCi> identity Mult :: Int
1
Perhaps more idiomatic would actually be to make the flag types empty
{-# LANGUAGE EmptyDataDecls #-}
data Add
data Mult
GHCi> identity (undefined :: Add) :: Int
0
GHCi> identity (undefined :: Mult) :: Int
1
This makes it clearer that the phantom argument actually carries no runtime information, just controls what instance the compiler chooses.
Admittedly, this is pretty ugly.
The right™ solution is to make newtype wrappers to contain the phantom information. In fact, such wrappers are already in the standard libraries: Sum
and Product
.
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