
I've got what's probably a beginner's question, but I'm out of ideas for solving it. It looks like it tripped me up in "Write Yourself a Scheme..." too, since the code there seems like it's arranged so I never ran into it... I've got a couple functions: binop :: (AmbrosiaData -> AmbrosiaData -> AmbrosiaM AmbrosiaData) -> AmbrosiaM () binop operator = do second <- popStack first <- popStack result <- operator first second pushStack result numNumOp :: Num a => (a -> a -> a) -> AmbrosiaData -> AmbrosiaData -> AmbrosiaM AmbrosiaData numNumOp op (Number val1) (Number val2) = return $ Number $ op val1 val2 numNumOp op (Float val1) (Float val2) = return $ Float $ op val1 val2 numNumOp op _ _ = throwError . TypeMismatch $ "Number" the intention being that I can define primitive operations for an interpreter with ("+", binop $ numNumOp (+)). In the Number case, val1 & val2 are Integers, while in the Float case, they're Floats. However, when I try to compile, I get: ./Primitives.hs:82: Cannot unify the type-signature variable `a' with the type `Integer' Expected type: Integer Inferred type: a In the application `op val1 val2' In the second argument of `($)', namely `op val1 val2' This makes no sense to me, since Integer is an instance of Num and op is defined as type Num a => a -> a -> a. Shouldn't the typechecker be able to instantiate 'a' with Integer and give the resulting type Integer -> Integer -> Integer, and then pass the final Integer to Number? Is my understanding of typeclasses off somehow? Any help would be appreciated. Regards, Jonathan

On Sun, Dec 03, 2006 at 12:26:30PM -0500, Jonathan Tang wrote:
I've got what's probably a beginner's question, but I'm out of ideas for solving it. It looks like it tripped me up in "Write Yourself a Scheme..." too, since the code there seems like it's arranged so I never ran into it...
I've got a couple functions:
binop :: (AmbrosiaData -> AmbrosiaData -> AmbrosiaM AmbrosiaData) -> AmbrosiaM () binop operator = do second <- popStack first <- popStack result <- operator first second pushStack result
numNumOp :: Num a => (a -> a -> a) -> AmbrosiaData -> AmbrosiaData -> AmbrosiaM AmbrosiaData numNumOp op (Number val1) (Number val2) = return $ Number $ op val1 val2 numNumOp op (Float val1) (Float val2) = return $ Float $ op val1 val2 numNumOp op _ _ = throwError . TypeMismatch $ "Number"
When you say Num a => (a -> a -> a) -> ..., what that means is: numNumOp :: forall a. Num a => ((a -> a -> a) -> AmbrosiaData -> AmbrosiaData -> AmbrosiaM AmbrosiaData) That is, what you pass must be a single type. It looks like what you want is: numNumOp :: (forall a. Num a => (a -> a -> a)) -> AmbrosiaData -> AmbrosiaData -> AmbrosiaM AmbrosiaData Notice that forall is in the argument - thus numNumOp must recieve a polymorphic argument. This is called rank-2 polymorphism, and numNumOp is a rank-2 polymorphic function. Unfortunately, it turns out that allowing foralls inside function arguments makes typechecking much harder, in general impossible. GHC allows it but requires explicit type annotations on all rank-2 polymorphic functions. Haskell98 forbids rank-2 polymorphism altogether (and explicit foralls, for that matter).

Stefan O'Rear wrote:
[...] Unfortunately, it turns out that allowing foralls inside function arguments makes typechecking much harder, in general impossible.
Just a tiny correction: AFAIK, it is type /inference/ which becomes undecidable in the presence of higher rank types -- checking works just fine (it could be true that it's harder, though). Cheers Ben
participants (3)
-
Benjamin Franksen
-
Jonathan Tang
-
Stefan O'Rear