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
Functoritself. Functoris an mappable object. Something implementsmapfunction.
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/catchhard 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