Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resolving ambiguous instance for multiparam type class

Tags:

haskell

ghc

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.

like image 634
bheklilr Avatar asked Aug 26 '13 21:08

bheklilr


2 Answers

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
like image 160
jberryman Avatar answered Oct 19 '22 13:10

jberryman


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.

like image 4
max taldykin Avatar answered Oct 19 '22 15:10

max taldykin