When I first started learning functional programming, there was always this function that intrigued me a lot. It is called the Identity Function. Its definition could not be any simpler, in lambda calculus it is
In Haskell it is even pre-defined in Prelude, Haskell’s standard library:
id :: a -> a
id x = x
Two lines of code, and the first line is not even strictly necessary because it is simply an explicit type annotation that the compiler could have inferred automatically. Nevertheless, it is a best practice to include that manually so there’s that.
On the surface, this function does not seem to do much — given any input x
, it is simply returning the same value x
back to the caller. But, you will see that it has many interesting properties.
Let us digress a bit and look at an example in C♯
On line 4 we are projecting the variable list
through a lambda expression x => x
and converting it to a List<int>
. Can you guess what the output is? Yes, it returns a list with all the original integers from 1
to 5
to it. If we ignore the difference in the variable references of the input and output list for a bit, you can see the input and output lists are exactly equal.
In fact, all list types exhibit similar qualities. When mapped over x => x
, it will always return a list with identical contents in it. And what is x => x
you ask? Yes, it is exactly the Identity Function in C♯.
There is a reason for this property because lists (at least in most standard library implementations of major programming languages) belong to a datatype called Functors.
What are functors exactly?
Without going too much into abstract category theory, functors can be thought of as a ‘container’ that wraps the underlying value. It provides a special higher-order function (fmap
in Haskell, map
in JavaScript, Select
in C♯) that allows you to transform the underlying values without knowing what the ‘containers’ are.
To elaborate using the C♯ example above, the lambda expression passed into Select
does not care about the list ‘container’, Select
only asks the caller to pass a function that consumes each individual element and outputs a new element. This abstraction takes the burden of understanding the list ‘container’ away from the caller of Select
.
Throughout the development of lambda calculus and Haskell, it was found that lists are not the only functors. There are also other ‘containers’ that work very similarly to how fmap
works on lists.
Four functor Haskell instances are shown here for your reference:
Lists
(Haskell’s immutable linked lists)Maybe
(Haskell’s way of statically representing the absence of values)Either
(Haskell’s way of statically representing a two-option choice)Function
(Yes even normal functions are functors)
The derivation is left as an exercise for the readers.
Functors and the Identity Function
Functors and the Identity Function have a very special relationship because a functor must observe several laws in order to be considered valid. One of them is the functor identity law:
fmap id = id
Which is merely a generalisation of the observation made from the C♯ example — when you fmap
over a ‘container’ through the Identity Function id
, it is equivalent to simply calling id
, which also means you can omit the whole thing, since id x
is equal to x
.
Thank you for reading! I hope this sparked your interest in functional programming and category theory!