Polymorphism (meaning “many forms”) allows code to work with values of different types. Haskell supports two main kinds of polymorphism: parametric polymorphism and ad-hoc polymorphism (via typeclasses).
Parametric Polymorphism
Parametric polymorphism allows functions to operate on values of any type, without knowing or caring what that type is.
Type Variables
Type variables (usually lowercase letters like a, b, c) represent arbitrary types:
id :: a -> a -- Identity function works on any type
length :: [a] -> Int -- Length works on lists of any type
reverse :: [a] -> [a] -- Reverse works on lists of any type
fst :: (a, b) -> a -- First works on any pair of typesExamples of Parametrically Polymorphic Functions
-- Identity function
id :: a -> a
id x = x
-- Apply a function twice
twice :: (a -> a) -> a -> a
twice f x = f (f x)
-- Swap elements in a pair
swap :: (a, b) -> (b, a)
swap (x, y) = (y, x)Parametrically Polymorphic Data Types
Data types can also be parametrically polymorphic:
-- List of elements of type a
data List a = Empty | Cons a (List a)
-- Binary tree with values of type a
data Tree a = Leaf | Node a (Tree a) (Tree a)
-- Maybe type (optional value)
data Maybe a = Nothing | Just a
-- Either type (value of one type or another)
data Either a b = Left a | Right bParametricity
Parametricity is a property of parametrically polymorphic functions: the behavior of a function is determined solely by its type signature, not by the types it operates on.
For example, a function with type [a] -> Int can only compute properties like length - it cannot inspect the elements themselves because it knows nothing about type a.
Consequences of Parametricity
- Free theorems: We get certain properties “for free” based on the type signature
- Limited operations: Functions can only perform operations consistent with all possible types
- Predictable behavior: Functions behave uniformly across all types
Example: Functions of Type a -> a
How many functions can have the type a -> a?
With parametricity, there’s only one possible function: the identity function id x = x.
This is because any function of type a -> a must work the same way for all types, and the only thing it can do is return its input unchanged.
Ad-hoc Polymorphism via Typeclasses
Ad-hoc polymorphism allows functions to behave differently depending on the type of their arguments. In Haskell, this is accomplished with typeclasses.
What are Typeclasses?
A typeclass defines a set of functions that must be implemented by any type that wants to be a member of that class.
class Show a where
show :: a -> StringThis says: “For a type a to be a member of the Show class, it must implement the show function.”
Typeclass Instances
Types become members of a typeclass by providing implementations of the required functions:
data Color = Red | Green | Blue
instance Show Color where
show Red = "Red"
show Green = "Green"
show Blue = "Blue"Typeclass Constraints
Functions can require that their type parameters belong to specific typeclasses:
-- A function that works on any type that can be shown
printTwice :: Show a => a -> IO ()
printTwice x = do
putStrLn (show x)
putStrLn (show x)The Show a => part is a typeclass constraint, saying “this works for any type a that is a member of the Show class.”
Common Typeclasses
Eq - Equality Testing
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
-- Default implementations
x /= y = not (x == y)
x == y = not (x /= y)Example instance:
instance Eq Color where
Red == Red = True
Green == Green = True
Blue == Blue = True
_ == _ = FalseOrd - Ordering
class Eq a => Ord a where
compare :: a -> a -> Ordering
(<), (<=), (>), (>=) :: a -> a -> Bool
max, min :: a -> a -> a
-- Default implementations providedOrd has Eq as a superclass, meaning any type that is Ord must also be Eq.
Num - Numeric Types
class Num a where
(+), (-), (*) :: a -> a -> a
negate, abs, signum :: a -> a
fromInteger :: Integer -> aThis allows numeric operations on various types (Int, Float, Double, etc.).
Show - String Conversion
class Show a where
show :: a -> StringRead - Parsing from Strings
class Read a where
read :: String -> aderiving Mechanism
For common typeclasses, Haskell can automatically generate instances:
data Color = Red | Green | Blue
deriving (Show, Eq, Ord, Enum)This automatically implements:
showforShow==and/=forEqcompare,<, etc. forOrdsucc,pred, etc. forEnum
Custom Typeclasses
You can define your own typeclasses:
class Drawable a where
draw :: a -> IO ()
clear :: a -> IO ()
data Shape = Circle Float | Rectangle Float Float
instance Drawable Shape where
draw (Circle r) = putStrLn $ "Drawing circle with radius " ++ show r
draw (Rectangle w h) = putStrLn $ "Drawing rectangle " ++ show w ++ "x" ++ show h
clear _ = putStrLn "Clearing shape"Comparing Parametric and Ad-hoc Polymorphism
| Parametric Polymorphism | Ad-hoc Polymorphism (Typeclasses) |
|---|---|
| One implementation works for all types | Different implementations for different types |
| Functions operate on the structure, not the content | Functions can inspect and operate on values |
| ”Uniform” behavior across types | Type-specific behavior |
Examples: id, map, reverse | Examples: show, (+), (==) |
Key Points to Remember
- Parametric polymorphism uses type variables to create functions that work on any type
- Parametricity restricts what polymorphic functions can do, leading to predictable behavior
- Typeclasses enable ad-hoc polymorphism, allowing different implementations for different types
- Common typeclasses include
Eq,Ord,Show,Read, andNum - The
derivingmechanism automates the creation of typeclass instances