I'm developing a specialized numerical data processing library, and I've come across an error that I can't figure out how to fix. I think it'll be easier to show an example first and then explain my problem. I also apologize for the strange names, I'm having to obfuscate for legal purposes.
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
data MyError = MyError String deriving (Eq, Show)
data MyList = MyList [Double] deriving (Eq, Show)
data NamedList = NamedList String MyList deriving (Eq, Show)
class MyNum a b ret where
myAdd :: a -> b -> Either MyError ret
myLessThan :: a -> b -> Either MyError Bool
instance MyNum MyList Double MyList where
myAdd (MyList xs) x = Right $ MyList $ map (+x) xs
myLessThan (MyList xs) x = Right $ all (< x) xs
instance MyNum NamedList Double NamedList where
myAdd (NamedList n l) x = fmap (NamedList n) $ myAdd l x
myLessThan (NamedList n l) x = myLessThan l x
If I try to compile this, I get the error
No instance for (MyNum MyList Double ret0)
arising from a use of `myLessThan'
The type variable `ret0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there is a potential instance available:
instance MyNum MyList Double MyList
-- Defined at testing_instances.hs:13:10
Possible fix:
add an instance declaration for (MyNum MyList Double ret0)
In the expression: myLessThan l x
In an equation for `myLessThan':
myLessThan (NamedList n l) x = myLessThan l x
In the instance declaration for `MyNum NamedList Double NamedList'
Because the compiler can't figure out which specific instance of MyNum
to use for MyList
. It works for myAdd
because the return type for MyNum
is easily derived, but it can't figure it out for myLessThan
. I want to use this typeclass so that I can easily add fine-grained error handling throughout, and because my actual code has the equivalent of +, -, *, /, <, <=, >, and >=, and I want to make an instance for MyNum Double MyList MyList
, MyNum MyList MyList MyList
, and similar ones for NamedList
. Unless there's an easier way to do this, it's so that I can have polymorphic commutative operators.
However, I can't figure out what type signature to add to myLessThan
for the second instance so that it can know which instance to use. I know one solution would be to split the arithmetic and comparison operators into two separate type classes, but I would like to avoid doing so if at all possible.
You can use functional dependencies to specify that "ret
is uniquely determined by a
and b
".
...
{-# LANGUAGE FunctionalDependencies #-}
...
class MyNum a b ret | a b -> ret where
...
This lets the typechecker know that it can pick the correct instance definition, knowing only the a
and b
from the arguments in your:
myLessThan (NamedList n l) x = myLessThan l x
The compiler will now complain if you define an additional instance with the same a
and b
but a different ret
, such as
instance MyNum MyList Double SomeOtherType where
As jberryman noted, you can use TypeFamilies
. And this is how:
-{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE TypeFamilies #-}
-class MyNum a b ret where
- myAdd :: a -> b -> Either MyError ret
+class MyNum a b where
+ type Ret a b
+ myAdd :: a -> b -> Either MyError (Ret a b)
-instance MyNum MyList Double MyList where
+instance MyNum MyList Double where
+ type Ret MyList Double = MyList
-instance MyNum NamedList Double NamedList where
+instance MyNum NamedList Double where
+ type Ret NamedList Double = NamedList
I just moved ret
type from class parameter to associated type Ret
.
It is a TypeFamily
-way to state that there is function from class parameters a
and b
to Ret
.
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