How to use GHC MultiParamTypeClass

118 Views Asked by At

I am trying to implement a "DrawEnv" type class indexed by a point type:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeSynonymInstances #-}

class Monad m => DrawEnv p m where
    box     :: p -> p -> m ()
    clear   :: m ()
    line    :: p -> p -> m ()

type Pos = (Float,Float)

instance DrawEnv Pos IO where
    box     p0 p1   = putStrLn $ "Box " ++ show p0 ++ " " ++ show p1
    clear           = putStrLn "Clear"
    line    p0 p1   = putStrLn $ "Line " ++ show p0 ++ " " ++ show p1

draw :: DrawEnv Pos m => m ()
draw = do
    clear
    box  (10.0,10.0) (100.0,100.0)
    line (10.0,10.0) (100.0,50.0)

However GHC isn't happy:

Could not deduce (DrawEnv (t0, t1) m) arising from a use of `box'
from the context (DrawEnv Pos m)
  bound by the type signature for draw :: DrawEnv Pos m => m ()
  at Code/Interfaces3.hs:63:9-29
The type variables `t0', `t1' are ambiguous
Relevant bindings include
  draw :: m () (bound at Code/Interfaces3.hs:64:1)
Note: there is a potential instance available:
  instance DrawEnv Pos IO -- Defined at Code/Interfaces3.hs:56:10
In a stmt of a 'do' block: box (10.0, 10.0) (100.0, 100.0)
In the expression:
  do { clear;
       box (10.0, 10.0) (100.0, 100.0);
       line (10.0, 10.0) (100.0, 50.0) }
In an equation for `draw':
    draw
      = do { clear;
             box (10.0, 10.0) (100.0, 100.0);
             line (10.0, 10.0) (100.0, 50.0) }

My question is why GHC doesn't accept this given the Pos constraint?

2

There are 2 best solutions below

1
On BEST ANSWER

This class definition won't work, because the type of clear doesn't mention the type variable p, so it is impossible to instantiate clear with a concrete type. Adding type signatures to box or line won't help - even clear :: IO () will produce a type error.

This could be fixed by adding a function dependency to your class:

class Monad m => DrawEnv p m | m -> p where

Then the rest of your code compiles. Alternatively, you could split your class into two classes:

class Monad m => MonadDraw m where 
  putStringLn :: String -> m () 

  clear   :: m ()
  clear = putStringLn "Clear"

class DrawEnv p where
  box     :: MonadDraw m => p -> p -> m ()
  line    :: MonadDraw m => p -> p -> m ()

instance (Fractional a, Show a, Fractional b, Show b) => DrawEnv (a,b) where
    box     p0 p1   = putStringLn $ "Box " ++ show p0 ++ " " ++ show p1
    line    p0 p1   = putStringLn $ "Line " ++ show p0 ++ " " ++ show p1

draw :: MonadDraw m => m () 
draw = do
    clear
    box  (10.0,10.0) (100.0,100.0)
    line (10.0,10.0) (100.0,50.0)
0
On

The code is ambiguous. Specifically we don't know the type of (10.0,10.0). For example it could be (Double,Double). It's most general type is (Fractional a,Fractional b) => (a,b).

The solution to this is to write

box  ((10.0,10.0) :: Pos) ((100.0,100.0)::Pos)

instead and fix the other lines similarly.