Mike
Mike

Reputation: 33

Haskell: Using the same operator on different types in a function

I'm writing a simple interpreter in Haskell. I have 3 possible variable types: bool, int and string. To avoid repetition in evaluating comparisons, I've written a function that takes 2 expressions and an operator:

data Value = IntVal Integer
           | StringVal String
           | BoolVal Bool
           | ...

evalComparison :: Ord a => Exp -> (a -> a -> Bool) -> Exp -> Result Value
evalComparison expr1 op expr2 = do
  val1 <- evalExp expr1
  val2 <- evalExp expr2
  return $ BoolVal $
    case (val1, val2) of
      (IntVal i1, IntVal i2)       -> op i1 i2 (*)
      (StringVal s1, StringVal s2) -> op s1 s2 (**)
      (BoolVal b1, BoolVal b2)     -> op b1 b2 (***)
      otherwise                    -> error "Expected values of the same type!"

It's intended usage is, for example:

evalExp :: Exp -> Result Value
...
evalExp (ELessThen e1 e2) = evalComparison e1 (<) e2

(And so on for other comparison operators).

The problem is - it doesn't work. GHC says it couldn't match type Integer with [Char] in line (**) and similarly Integer with Bool in line (***).

I think I know where the problem lies: once the a from the operator's type is determined as Integer in line (*), it cannot be changed. So my question is twofold:

  1. Why does this problem arise, provided that the result type (Bool) is always the same regardless of the operator's arguments' types?

  2. What can be done to make this work?

Upvotes: 2

Views: 383

Answers (1)

MathematicalOrchid
MathematicalOrchid

Reputation: 62848

The type signature a -> a -> Bool says that there must exist some type a for which op has that type. But you want this to work for more than one type a. You can't do that in Haskell '98.

If you turn on rank-2 types (or rank-N types), then you can do

evalComparison :: Exp -> (forall a. a -> a -> Bool) -> Exp -> Result Value

This says that whatever you pass in as op has to work for multiple types a. In fact, it says that op has to work for all possible types a. But that's probably too much. What you want is probably closer to

evalComparison :: Exp -> (forall a. Ord a => a -> a -> Bool) -> Exp -> Result Value

This says that op has to work for every possible a that implements Ord.

Although frankly at that point, you might as well just call compare explicitly in your case-expression. Have evalComparison return an Ordering, and then apply a Ordering -> Result Value to that. Ordering is only one type, which should make things simpler.

Upvotes: 1

Related Questions