简体   繁体   中英

Polymorphism within higher-order functions?

I have an algebraic data type with some constructors that hold comparable values, and some constructors that don't. I wrote some comparison functions that work like the standard (==) and (/=) operators, but return Nothing for comparisons that don't make sense:

data Variant = IntValue Int
             | FloatValue Float
             | NoValue

equal :: Variant -> Variant -> Maybe Bool
equal (IntValue a) (IntValue b) = Just (a == b)
equal (FloatValue a) (FloatValue b) = Just (a == b)
equal _ _ = Nothing

unequal :: Variant -> Variant -> Maybe Bool
unequal (IntValue a) (IntValue b) = Just (a /= b)
unequal (FloatValue a) (FloatValue b) = Just (a /= b)
unequal _ _ = Nothing

That works, but the repetition is unwieldy — especially since I actually have more Variant constructors and more comparison functions.

I thought I could factor out the repetition into a helper function that's parameterized on the comparison function:

helper :: (Eq a) => (a -> a -> Bool) -> Variant -> Variant -> Maybe Bool
helper f (IntValue a) (IntValue b) = Just (f a b)
helper f (FloatValue a) (FloatValue b) = Just (f a b)
helper _ _ _ = Nothing

equal' :: Variant -> Variant -> Maybe Bool
equal' = helper (==)

unequal' :: Variant -> Variant -> Maybe Bool
unequal' = helper (/=)

but that doesn't work because the type variable a apparently can't bind to both Int and Float at the same time in the definition of helper ; GHC binds it to Float and then complains about a type mismatch on the line that handles IntValue .

A function like (==) is polymorphic when used directly; is there a way to pass it to another function and have it remain polymorphic?

Yes, this is possible, but only with language extensions :

{-# LANGUAGE Rank2Types #-}

helper :: (forall a. (Eq a) => (a -> a -> Bool))
       -> Variant -> Variant -> Maybe Bool
helper f (IntValue a) (IntValue b) = Just (f a b)
helper f (FloatValue a) (FloatValue b) = Just (f a b)
helper _ _ _ = Nothing

The forall a. does about what it sounds like; the a is universally quantified within the parentheses, and out of scope outside them. This means that the f argument is required to be polymorphic over all types a that are instances of Eq , which is exactly what you want.

The extension here is called "rank 2" because it allows the regular style of polymorphism at the outermost scope, plus polymorphic arguments as in the example here. To nest things further, you need the extension RankNTypes , which is fairly self-descriptive.

As an aside, regarding higher-rank polymorphic types--keep in mind that the forall is what actually binds the variable to a type; you can think of them as behaving a lot like a lambda, in fact. When you apply such a function to something with a concrete type, the type of the argument is implicitly bound by the forall for that use. This comes up, for instance, if you try to use a value whose type was bound by an inner forall outside that function; the value's type has gone out of scope, which makes it difficult to do anything sensible (as you can probably imagine).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM