writing binop to work with int and floats

haskell, typechecking

Solution

This is called higher rank types, basically the type you want is

 binop :: (forall a. Num a => a -> a -> a) -> [Value] -> EvalM Value

But what you have is

 binop :: forall a. Num a => (a -> a -> a) -> [Value] -> EvalM Value

Do you see the difference? With the first function, the operator is actually polymorphic within the function, it says "Given a function that takes any `Num a` of type `a -> a -> a` ...". The second one says, "For all `a`, given a function from a single arbitrary `a -> a -> a` ...".

Luckily GHC supports higher rank types,

{-# LANGUAGE RankNTypes #-}

...
binop :: (forall a. Num a => a -> a -> a) -> [Value] -> EvalM Value
binop (+) [VFloat a, VFloat b] = return $ VFloat (a + b)
binop (+) [VInt   a, VFloat b] = return $ VFloat (fromInteger a + b)
binop (+) [VFloat a, VInt   b] = return $ VFloat (a + fromInteger b)
binop (+) [VInt   a, VInt   b] = return $ VInt   (a + b)
binop  _  [_,_] = throwError "Currently only adding numbers"
binop  _  _     = throwError "Arity Error: Add takes 2 arguments"

However the type inferencer doesn't do so hot with higher rank types, so you will likely have to add explicit signatures.

Problem

I have a parser that I am working on. Without getting into the all the details, I want a function that will add two numeric values do: ``` add [VFloat a, VFloat b] = return $ VFloat (a + b) add [VInt a, VFloat b] = return $ VFloat (fromInteger a + b) add [VFloat a, VInt b] = return $ VFloat (a + fromInteger b) add [VInt a, VInt b] = return $ VInt (a + b) add [_,_] = throwError "Currently only adding numbers" add _ = throwError "Arity Error: Add takes 2 arguments" ``` Cool, works great. Now I want the same function for `-`,`*`,`/`,`<`,`>`,`==`,etc... So I factor out the `+` operator and go to pass in an operator `op :: Num a => a->a->a` right? Well not quite. If I just replace the `+` with `'op'` the type-checker tells me that op is actually `Double -> Double -> Double` based on the first three versions and therefore it cannot be applied to `Integer`s in the fourth version. Two questions: - How do I write `binop :: Num a => (a->a->a) -> [Value]-> EvalM Value` so that it can handle both VInt and VFloat? - What is the proper name for the situation I am facing so I can Google the answer next time?

Original source

Related problems