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?
This is called higher rank types, basically the type you want is
But what you have is
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 typea -> a -> a
...". The second one says, "For alla
, given a function from a single arbitrarya -> a -> a
...".Luckily GHC supports higher rank types,
However the type inferencer doesn't do so hot with higher rank types, so you will likely have to add explicit signatures.