Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 676
Wyzard Avatar asked Aug 15 '11 03:08

Wyzard


People also ask

What do you mean by higher-order functions?

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.

What are polymorphic functions?

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.

What are higher-order functions give examples?

Note: Functions such as filter(),map(),reduce(),some() etc, these all are example of Higher-Order Functions.

Why it is better to use 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.


1 Answers

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).

like image 121
C. A. McCann Avatar answered Oct 31 '22 08:10

C. A. McCann