8
votes

I have type classes, for all of which I would like to have some common behavior. My problem is explained in the following code:

class A a
class B b

class X x where
    method :: (A a, B b) =>  x -> a -> b

data T = L | M | N
data U = P | Q | R

instance A T
instance B U

data Y = ZZZ

instance X Y where
    method _ L = P
    method _ M = Q
    method _ N = R

When I load this module, I get the following error:

example.hs:19:14:
    Could not deduce (a ~ T)
    from the context (A a, B b)
      bound by the type signature for method :: (A a, B b) => Y -> a -> b
      at example.hs:(17,5)-(19,18)
      `a' is a rigid type variable bound by
          the type signature for method :: (A a, B b) => Y -> a -> b
          at example.hs:17:5
    In the pattern: N
    In an equation for `method': method _ N = R
    In the instance declaration for `X Y'

example.hs:19:18:
    Could not deduce (b ~ U)
    from the context (A a, B b)
      bound by the type signature for method :: (A a, B b) => Y -> a -> b
      at example.hs:(17,5)-(19,18)
      `b' is a rigid type variable bound by
          the type signature for method :: (A a, B b) => Y -> a -> b
          at example.hs:17:5
    In the expression: R
    In an equation for `method': method _ N = R
    In the instance declaration for `X Y'
Failed, modules loaded: none.

I am at loss what to do in this case. Even when T and U are instance of A and B, I get this error. If I cannot return a rigid type value from method, how do I code this part?

2

2 Answers

11
votes

The signature method :: (A a, B b) => x -> a -> b promises that method works for every pair of types (a, b) with a an instance of A and b an instance of B, but you define it to work only with two specific types.

This is fundamentally different from interfaces in Java or the like, where the callee chooses which type is used, the only thing the caller knows is that interface X is implemented. In Haskell, given such a signature, the caller decides which types are used (here, what type is passed as second argument and what type shall be returned) and the callee has to be able to provide the demanded functionality (as long as the demanded types are instances of the required classes).

Without any methods in classes A and B to analyse respectively construct values of an instance of that class, you cannot implement method other than with undefined (various degrees of undefinedness are possible due to seq), so you would have to tell the world that you are in fact using T and U.

Another way is to make X a multiparameter type class,

{-# LANGUAGE MultiParamTypeClasses #-}

class (A a, B b) => X x a b where
    method :: x -> a -> b

However, that might require functional dependencies to resolve instances. Another way would be to use associated types,

{-# LANGUAGE TypeFamilies #-}

class X x where
    type AType x
    type BType x
    method :: x -> AType x -> BType x

instance X Y where
    type AType Y = T
    type BType Y = U
    method ...
3
votes

It's really hard to think about a way to rescue your example (for the reasons mentioned by Daniel Fischer). If you have two totally unrelated type classes A and B, how should you establish a general connection? So you could put both together, but I doubt that this is what you want:

{-# LANGUAGE MultiParamTypeClasses #-}

class A2B a b where
  convert :: a -> b

data T = L | M | N
data U = P | Q | R

instance A2B T U where
  convert L = P
  convert M = Q
  convert N = R  

data Y = ZZZ

class X x where
    method :: (A2B a b) =>  x -> a -> b

instance X Y where
    method _ t = convert t

And this design isn't very stable either: You'll get in trouble when you want to have instances like A2B T V next to A2B T U.