Polymorphic types

We study the concept of polymorphic types in Haskell and write polymorphic versions of previously written functions.

Introduction to polymorphism

So far, all the functions we have written ourselves operated on concrete types. For example, the containsMatchingInt function from the previous lesson operates on functions of type Int -> Bool and lists of type [Int]. It can not process functions or lists of any other type.

containsMatchingInt :: (Int -> Bool) -> [Int] -> Bool
containsMatchingInt test [] = False
containsMatchingInt test (x:xs) | test x == True = True
                                | otherwise      = containsMatchingInt test xs

The concept of containsMatching, however, is more general. We could write a similar function for lists of doubles, where the predicate also changes to a test on doubles.

containsMatchingDouble :: (Double -> Bool) -> [Double] -> Bool
containsMatchingDouble test [] = False
containsMatchingDouble test (x:xs) | test x == True = True
                                   | otherwise      = containsMatchingDouble test xs

Similarly, one might write containsMatchingChar, containsMatchingString, and so on. But all of these functions use the same general concept of containsMatching. Instead of writing the same logic for each concrete type, we should strive to implement a general containsMatching function that can work on lists of any type.

We know functions that behave like this from the Haskell Prelude. Some examples are the head and tail functions that work on lists of arbitrary element types. These functions are polymorphic, which means that they can be applied not only to arguments of just one concrete type, but to a whole range of argument types. In this case, lists of any kind. Let’s see how we can write polymorphic functions ourselves.

Get hands-on with 1200+ tech skills courses.