“A mathematician, like a painter or a poet, is a maker of patterns. If his patterns are more permanent than theirs, it is because they are made with ideas.”
— G.H. Hardy, A Mathematician’s Apology
Sometimes, logical accuracy is not possible because of intrinsic complexity of the data, in practical programming. Data Abstraction is a very useful tool which helps us in creating a simplified representation of the data.
In order to achieve this, we create ‘Containers’, these containers only and only contain our data and do nothing else, we don’t provide them properties or methods like in OOP.
We take a value and put it inside our container, the container will keep our value safe, while we pass it on through our beautiful functional logic. We’ll only take that value out when we need that urgently. In this way our container will have only two tasks:
- Keeping the value inside itself.
- Giving us the value back, only when we require it.
Also, never mutate the value inside them.
When we do functional programming, these containers are very powerful, as they contribute to the base of our functional constructs, and help us with common techniques like Pure Functional Error Handling and Asynchronous Actions (and many more things).
Before we go on in deep about these containers, we are going to talk about a special type of containers called Functors.
So, What are Functors? Functors are the containers that can be used with ‘map’ function.
Before we create our own containers, let’s look at some common containers we’ve been using since our childhood.
Arrays are the most common containers we use in our daily programming, instead of containing just one value they contain several. They are the simplest of all Data Abstractions, yet so powerful. Let’s work with them,
const arr = [ 8, 10, 23, 35, 54 ];
and we can get the values out of them like
const b = a;
Now, if you promise to use only these operations on the arrays, I’ll promise you that these arrays will be your friends forever.
Never modify the original array like,
arr = 45 ❌
But you can always use them to create new ones,
const arr2 = [ ...arr, 38, 52 ]
const even = filter(x => x%2 === 0, arr)
We never ever apply any methods on arrays that may change the value inside them, we just create the arrays or get the values out, that’s it.
While the Java guys will say with doing this I’ve rendered my array useless, but I’ll say, “No, I have just created my array so powerful than you can ever imagine”.
And yes Array is a Functor.
A Functor is a container which can be mapped upon by a Unary function.
When I say mapped upon, I mean that this container can be treated with a special function(say fmap or map) which applies any unary function to every content of this container and returns a container with the resulting values as its contents.
In case of arrays, the special function is simply called the map function.
The Map Function
Map function in arrays takes an array and applies a particular function to all of its elements one by one and returns another array.
where multiplyBy2 = x => x * 2 and map = (fn, arr) => arr.map(fn)
Since, we’ll always be getting another array from the map, we can always map it again to create a chain of array transformations.
[1,2,3].map(x => x * 3).map(x => x * 2).map(x => x / 6)
A Map function is more than an iterator function, remember, when our value is inside a container we cannot just directly apply a function on it and expect the value to be changed. For example,
const a = [1, 2, 3]
String(a) = ‘[1 ,2, 3]’ and not [‘1’, ‘2’, ‘3’]
A map function gives a function access to the contents of the container.
map(String, [1, 2, 3]) = [‘1’, ‘2’, ‘3’]
Also, a map function never changes the container, instead it just act upon its contents. Keeping the container retained.
A map will not change the type of the container but it can change the type of its contents.
The type of the contents may change, and we can see that from the type definition of the map function.
map :: (a -> b) -> [a] -> [b]
fmap :: (a -> b) -> F a -> F b
b can be of same type or different type.
Now, if you squint your eyes really hard, you’ll see that map function is taking a function from
a -> b and returning a function from
Fa -> Fb
a -> b means any unary function that takes
a and returns
multiplyBy2(3) = 6 // is a -> b as 3 -> 6
Fa -> Fb means any unary function that takes a Container with
a inside and returns a Container with
multiplyArrBy2() =  // is Fa -> Fb as  -> , F is 
To understand how we are using these types read this,
Now how is our map function is changing
multiplyArrBy2 let’s see this,
For now our function
multiplyBy2 which used to act upon integers, now because of
map will act upon array of integers. In a way, our map function promoted or lifted our function so that it can act on our containers or arrays in this case.
We occasionally lift our functions so that they can act upon our data inside our containers. (Because normal functions can’t)
We use partially applied map function whenever possible because then we can use it to create another functions or we can use this with compose function, and when there’s an extreme urgency of data, we’ll use our normal map function.
Why should we use partially applied functions?
Providing function with fewer arguments than it expects is called Partial Application of functions.hackernoon.com
Before going any further, we know that we use containers to hold our values and their only purpose is to hold them. We also know about Functors which are special containers on which we can do map operation. We know how a map function is used on Arrays, also we’ve met our first Container and a Functor, Array which we use frequently in our daily programming.
Now, let’s meet our second container which is also a functor. This container was with us all along from the starting of our functional adventures.
All Functions are functors too and hence are also containers.
How Functions are containers?
Okay, containers contain data, but our functions clearly contain logic, so how can functions be containers.
Well if you think it through: a function, when called, returns a value. So in a way, it is containing our value, the only difference is that the value is dynamically computed.
aFunction(45) // => 90
So aFunction gives the value 90, when it is passed 45
Think of them as arrays of infinite number of values and if you want a particular value from those, you need to call the function with the particular argument or arguments.
So, Functions are containers like arrays?
Yes, just like arrays give value when an index is passed, functions give result when an argument is passed.
const a = [ 8, 10, 23, 35, 54 ]
const f = z => z * 2
a = 10
f(2) = 4
Only our arrays are weaker as they only give results for ranged integers only as index, but our functions can take any type of arguments because there’s no limit to them, they can also take other functions as arguments too.
Functions are containers with infinite no. of values.
Since they are Functors they have a map too?
Yes, it is defined like this
const fnMap = (f, mappingFn) => (x => f(mappingFn(x)))
Just like the map function takes the array, applies the function to the contents and returns the array. Similarly, fnMap takes a function, applies another function to its result and returns the function, in a way it’s combining two functions such that the result of one function is the argument of the second function.
Hmm, okay let’s use this
const multiplyBy6 = fnMap(multiplyBy2, multiplyBy3)
This looks familiar, we’ve seen this before.
It is our Compose function.
So, our functions too have a map function and if we are mapping one function to another, we are kind of fusing those two functions. A map over function is called a Compose function.
- Let’s say we have a function
- Before taking out the value from it or say calling it, we mapped it with
- So all the values inside
multiplyBy2are multiplied by 3.
- Now, whenever we call
multiplyBy2with let’s say
xthe value we get will be
x * 3 * 2.
Perhaps, now this will be clearer,
const fnMap = (f, mappingFn) => (x => f(mappingFn(x)))
Functions are like arrays, a type of data abstraction. Only functions compute the data according to our requirements .
Or we can say that Arrays are like functions they just give away their result instantly when we use ‘’ on them.
We can bend our mind in any way to understand this, it’s all true.
We only have to remember one thing that our value is only valuable, if its inside a container, either a function, or an array or a container we have created only then we can break down complex structures in our code, possibly arising due to complexity in data, to simpler ones.
From now on, I need you remember this thing, in functional programming never use your data naked. Always wrap it inside a container.
Now, this article is too long to discuss more about Functors. But I promise, we’ll continue this in another one.
Written with 💖.