(Previous series about Container)
(fp-ts series 2: Functor)
- 👇🏻 What is Functor?
Functor in Programming
The term Functor could be confusing. Basically when we say Functor in programming sense, we refer to a “Container” that is “Mappable”. When you provide a “map” function that takes a function and transform it into a corresponding function in the “Container” world, it becomes “Mappable”.
When we have a “Container” that is “Mappable” and a “return” function that takes a data and transform it into the “Container” world, then you have a Functor! (Well, you still need to fulfil the Functor laws :) )
Map and Return function
Here is the type signature of “map” and “return” function in a functor.
/**
* The Map and Return function type signature in a functor
*/
type Map = (f: (a: A) => (b: B)) => (fa: Container<A>) => (fb: Container<B>)
type Return = (a: A) => (fa: Container<A>)
The “map” function takes a function that transforms the data into another type, then it takes a data wrapped with a “Container” and return the transformed data wrapped with the “Container”. The “map” function is also commonly known as “lift” if you look at the type signature this way:
/**
* map is also known as lift
*/
type Lift = (f: (a: A) => (b: B)) => ((fa: Container<A>) => (fb: Container<B>))
After it takes a function (f: (a: A) => (b: B)
), it returns a “lifted” version function ((fa: Container<A>) => (fa: Container<B>)
).
The implementation of “map” function should help you to unwrap the data from the “Container” (from fa
to a
), perform the transformation using the function f
(from a
to b
), and then wrap the result b
with the “Container” and return it (fb
).
The “return” function is much simpler, it should just helps you to the wrap the data with the “Container” (from a
to fa
).
Example — <Option> in fp-ts
Let’s take a look at the implementation of “map” and “return” function in <Option> in fp-ts.
/**
* Note: "return" is named as "of" in fp-ts (probably "return" is a reserved keyword in JS)
*/
/**
* Type in Option.d.ts
*/
export declare const map: <A, B>(f: (a: A) => B) => (fa: Option<A>) => Option<B>
export declare const of: Pointed1<URI>['of']
/**
* Implementation in Option.js
*/
var map = function (f) { return function (fa) {
return (0, exports.isNone)(fa) ? exports.none : (0, exports.some)(f(fa.value));
}; };
exports.of = exports.some;
(Quick review on Option)
In the implementation of “map” function in Option, it checks whether the Option value is None
or Some(a)
. If it is None
, it will just return None
. Otherwise, it unwraps the value from Some(a)
to a
, apply the function f
on the data a
and return the result data b
wrapped with Option (Some(b)
).
For the “return” (of
) function in Option, it will just help you to wrap the data with Option (from a
to Some(a)
).
Functor laws
There are two functor laws you need to follow if you are going to create your own functor: identity law and composition law.
/**
* identity law:
* map(id) = id
* composition law:
* map(compose(f, g)) = compose(map(f), map(g))
*/
id
function is simply a function that returns data itself(a => a
). The identity law tells us that after the “map” function takes the id
function, it must the same as the id
function in the “Container” (fa => fa
).
The composition law tells us that when we compose two functions f
and g
, and then feed it into the “map” function, the resulting function should be the same as if you feed the function f
and g
into the “map” function first respectively and compose them afterwards.
What’s Next:
Next, we will discuss more on Applicative Functor, Monad, and the Monoid in depth.
More about fp-ts.
Thanks for reading 🫡
Hope you enjoy this article and gain something from it. I wish it is less daunting now for you to learn Functional Programming :)
Ka Hin