Preface
This is another note for introducing some common terms or words that you may see in functional programming world.
Examples here will be implemented in either vanilla JS or Ramda - a functional library that focuses on functional programming than other general-purpose same purpose libraries.
Map
Map
is a function which transforms data from one to any type (including itself):
1 | // array of number to array of number (transform to itself) |
Filter
Filter
is a function which takes a predicate (a function takes an argument and return a boolean) and an list and finally returns an list:
1 | // array of number to array of number |
Reduce
Reduce
takes a function, an initial value and transforms data to any type (including itself):
1 | // array of number to nunber |
Transduce
You might find that you can implement map
and filter
in reduce
:
1 | const map = (f, arr) => { |
Basically, reduce
does two things at same time - transform and accumulation, but in FP, a good function only does one thing at a time.
Separating helps function composition and code reusability. We can achieve that by using transduce
- a function takes transducer
, accumulator
functions and an initial value as arguments.
1 | // Ramds's transduce. |
Curry
Outside the world of FP, functions can only be carried out when all arguments have been passed in.
1 | const add = (x, y) => x + y; |
In FP world, functions will be executed only when the last argument has been passed in. A curried function with partially arguments being passed in is called partially applied function.
1 | // Here I use Ramda's curry to make a function curried |
And here is a simple curry
implementation taking up to 2 arguments only:
1 | const curry = (fn) => (x) => (y) => fn(x, y); |
Curry is good for function composition, I will introduce them in next section.
Compose
compose
is a function that takes many transformers and compresses them into a function. This is also called pipe
or sequence
in other languages or libraries.
For the sake of simplicity I only make compose
take two functions. In practical world compose
can actually take as many functions as you want, not limit to two.
1 | const compose = R.curry((f, g) => (val) => f(g(val))); |
Beware of its evaluation order - f(g(val))
, functions will be evaluated in upside down. If you think it is not human friendly, in Ramda you can use pipe to get it evaluated from top to down.
Here it’s why curry
shines. The benefit of curry
is when using with compose
, we don’t have to mention data when building our transformer. Processing data without mentioning at initial is called point-of-free or pointfree. In other words we only care about our data’s name when we start transforming.
1 | const add1 = (x) => x + 1; |
Functor
Functor
in short explanation is:
- a container traps values inside.
- container can trap anything, like function or even
Functor
itself. Functor
is an mappable object. Something implementsmap
function.
Here is a Functor
implemented in OOP.
1 | class Functor { |
There is a very common example of Functor
that every JS developer is familiar with - array.
1 | [1,2,3].map(x => x + 1); |
There are two common advanced types of Functor
- one is Maybe
and the other is Either
.
Maybe
In other languages like Scala or OCaml, Maybe
is also called Option
. Maybe
is a type which represents something or nothing.
This is especially good when dealing with Functors
containing a null value, because when you try to apply function to null value, it fails softly.
You won’t confuse your user with an wild error or stuck them in plain white screen.
1 | class Maybe extends Functor { |
Either
Most programming languages have throw/catch
mechanics for error handling,
however throw/catch
isn’t nice in FP because of two reasons:
- it breaks the composition of functions.
throw/catch
hard fails program if errors were not caught. This is not a pure action.
Either
is the right candidate for this scenario. Basically it’s like ternary operator condition ? exp1 : exp2
,
except it’s a function:
1 | either(left, right, val) |
It execute/return right
only when the condition of right
has been fulfilled, otherwise left
instead.
Here it’s a simple implementation of Either
1 | class Either{ |
Applicative Functor
Applicative Functor (AP)
is an advanced type of Functor
. I just mentioned Functor
can contain anything including functions, and one of the definition of Functor
is it’s mappable. Now here comes a question - how do you map
a function?
The answer is we use ap
to apply arguments to functions:
1 | class Functor{ |
lift
Let’s take a look at this example more closely:
1 | Functor.of(add).ap(Functor.of(5)).ap(Functor.of(6)); |
This syntax is not quite elegant and functional enough. What if I already have add
function and I want to make it able to automatically process it’s argument in Functor
level?
1 | // As we know `Array` is a common kind of Functor. |
let’s use lift
to lift its arguments to functor without using Functor.of(x)
1 | // Use Ramda's add because it's already curried. |
Monad
There is another scenario - anything can be a value of Functor
, including Functor
itself.
1 | Functor.of( |
With Functor
in this status, our map
magic doesn’t work. In order to apply function to our value, we have to flatten it first.
1 | class Functor{ |
The flatten
and map
approach is called flatMap
. flatMap
is also called chain
in JS or other languages.
References
- functional programming jargon
- 函数式编程入门教程
- Reduce 和 Transduce 的含义
- Understanding Transducers in JavaScript
- Functors, Applicatives, and Monads in Plain English
- category theory for beginner
- mostly adequate guide
- Javascript Functor, Applicative, Monads in pictures
- Can’t wrap my head around “lift” in Ramda.js