Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multi-parameter typeclass instance declarations

Tags:

haskell

I am trying to understand multiparameter typeclasses but I just don't get the instance declarations. I am starting out trying to make an InnerProductSpace typeclass for a Vector type so that I can perform a dot product on two vectors. To start out I just wanted to see if I could multiply the first element of each vector. Here is my code

class InnerProductSpace a b c where
dot :: a -> b -> c

data Vector = Vector [Double]
  deriving (Show)

instance InnerProductSpace Vector Vector Double where
  dot (Vector a) (Vector b) = (head a * head b)

and the error after trying to use the dot function is

No instance for (InnerProductSpace Vector Vector c0)
  arising from a use of `dot'
The type variable `c0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there is a potential instance available:
  instance InnerProductSpace Vector Vector Double
    -- Defined at Vector.hs:8:10
Possible fix:
  add an instance declaration for
  (InnerProductSpace Vector Vector c0)
In the expression: dot a b
In an equation for `it': it = dot a b

What did I do wrong? Thanks!

like image 535
user2658647 Avatar asked Aug 06 '13 22:08

user2658647


2 Answers

The problem is that the compiler doesn't know how to choose the right instance given what it knows. If you try something like

vectorA `dot` vectorA

the compiler goes searching for the right dot knowing that its type must be dot :: Vector -> Vector -> c0. Unfortunately, that's just not enough information by itself—c0 could be anything and the compiler never assumes that just because it only has a single instance it must be the correct one (this is related to the open world assumption---there might be another instance out there the compiler hasn't seen yet, so it prefers to just throw an error). You could get away with it by explicitly telling the compiler what the result ought to be

vectorA `dot` vectorB :: Double

but this is tedious and likely to fail a lot with numeric types since so often those types are generic as well. For instance, which instance of Num is used here?

(vectorA `dot` vectorB) + 3

We know it's Double, but the compiler can't prove that.

One solution is to use Type Families as @AndrewC suggests in the comment. I actually highly recommend that. The other solution you'll see in the wild is Functional Dependencies. These are written like this

class InnerProductSpace a b c | a b -> c where
  dot :: a -> b -> c

and translate to entail a promise to the compiler: "knowing a and b is enough information to uniquely identify c". The compiler keeps you honest as well as it'll prevent you from writing instances that conflict on that promise.

instance InnerProductSpace Vector Vector Double where
  dot (Vector a) (Vector b) = (head a * head b)

instance InnerProductSpace Vector Vector Float where
  dot (Vector a) (Vector b) = (head a * head b)

---

/Users/tel/tmp/foo.hs:10:10:
    Functional dependencies conflict between instance declarations:
      instance InnerProductSpace Vector Vector Double
        -- Defined at /Users/tel/tmp/foo.hs:10:10
      instance InnerProductSpace Vector Vector Float
        -- Defined at /Users/tel/tmp/foo.hs:13:10

But the promise gives the compiler exactly enough information to resolve the Double in the previous example.

Main*> (Vector [1,2,3]) `dot` (Vector [2,3,4]) + 3.0
5
like image 175
J. Abrahamson Avatar answered Nov 07 '22 12:11

J. Abrahamson


or use TypeFamilies

class (D a b ~ c) => InnerProductSpace a b c where
  type D a b
  dot :: a -> b -> c

or

class InnerProductSpace a b where
  type D a b :: *
  dot :: a -> b -> D a b

instance InnerProductSpace Vector Vector where
  type D Vector Vector = Double
  dot (Vector a) (Vector b) = (head a * head b)
like image 43
viorior Avatar answered Nov 07 '22 11:11

viorior