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?
In mathematics and computer science, a higher-order function (HOF) is a function that does at least one of the following: takes one or more functions as arguments (i.e. a procedural parameter, which is a parameter of a procedure that is itself a procedure), returns a function as its result.
A function that can evaluate to or be applied to values of different types is known as a polymorphic function. A data type that can appear to be of a generalized type (e.g. a list with elements of arbitrary type) is designated polymorphic data type like the generalized type from which such specializations are made.
Note: Functions such as filter(),map(),reduce(),some() etc, these all are example of Higher-Order Functions.
Benefits of higher-order functionsThey are an excellent way to abstract and separate actions in a program. They are easy to reuse. They provide simplicity to the code, allowing the programmer and any other party to understand the code at a high level easily.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With