mostly adequate guide
This is a book on the functional paradigm in general. We'll use the world's most popular functional programming language: JavaScript. I believe it is the best way to learn FP for several reasons:
- You likely use it every day at work.
- We don't have to learn everything up front to start writing programs.
- The language is fully capable of writing top notch functional code.
What Ever Are We Doing?
Introductions
Now, there are some general programming principles -
various acronymic credos that guide us through the dark tunnels of any application: DRY (don't repeat yourself), YAGNI (ya ain't gonna need it), loose coupling high cohesion, the principle of least surprise, single responsibility, and so on.
A Brief Encounter
class Flock {
constructor(n) {
this.seagulls = n;
}
conjoin(other) {
this.seagulls += other.seagulls;
return this;
}
breed(other) {
this.seagulls = this.seagulls * other.seagulls;
return this;
}
}
const flockA = new Flock(4);
const flockB = new Flock(2);
const flockC = new Flock(0);
const result = flockA
.conjoin(flockC)
.breed(flockB)
.conjoin(flockA.breed(flockB))
.seagulls;
// 32It is unreasonably difficult to keep track of the mutating internal state.
const conjoin = (flockX, flockY) => flockX + flockY;
const breed = (flockX, flockY) => flockX * flockY;
const flockA = 4;
const flockB = 2;
const flockC = 0;
const result =
conjoin(breed(flockB, conjoin(flockA, flockC)), breed(flockA, flockB));
// 16Well, this time we got the right answer. With much less code. The function nesting is a tad confusing..
const add = (x, y) => x + y;
const multiply = (x, y) => x * y;
const flockA = 4;
const flockB = 2;
const flockC = 0;
const result =
add(multiply(flockB, add(flockA, flockC)), multiply(flockA, flockB));
// 16// associative
add(add(x, y), z) === add(x, add(y, z));
// commutative
add(x, y) === add(y, x);
// identity
add(x, 0) === x;
// distributive
multiply(x, add(y,z)) === add(multiply(x, y), multiply(x, z));// Original line
add(multiply(flockB, add(flockA, flockC)), multiply(flockA, flockB));
// Apply the identity property to remove the extra add
// (add(flockA, flockC) == flockA)
add(multiply(flockB, flockA), multiply(flockA, flockB));
// Apply distributive property to achieve our result
multiply(flockB, add(flockA, flockA));const conjoin = (flockX, flockY) => flockX + flockY;
const breed = (flockX, flockY) => flockX * flockY;
const flockA = 4;
const flockB = 2;
const flockC = 0;
const result = conjoin(breed(flockB, conjoin(flockA, flockC)), breed(flockA, flockB));
// 16First Class Functions
const hi = name => `Hi ${name}`;
const greeting = name => hi(name);
hi; // name => `Hi ${name}`
hi("jonas"); // "Hi jonas"
const greeting = hi;
greeting("times"); // "Hi times"let's examine a few more fun examples excavated from the library of npm packages.
// ignorant
const getServerStuff = callback => ajaxCall(json => callback(json));
// enlightened
const getServerStuff = ajaxCall;// this line
ajaxCall(json => callback(json));
// is the same as this line
ajaxCall(callback);
// so refactor getServerStuff
const getServerStuff = callback => ajaxCall(callback);
// ...which is equivalent to this
const getServerStuff = ajaxCall; // <-- look mum, no ()'sconst BlogController = {
index(posts) { return Views.index(posts); },
show(post) { return Views.show(post); },
create(attrs) { return Db.create(attrs); },
update(post, attrs) { return Db.update(post, attrs); },
destroy(post) { return Db.destroy(post); },
};const BlogController = {
index: Views.index,
show: Views.show,
create: Db.create,
update: Db.update,
destroy: Db.destroy,
};Why Favor First Class?
httpGet('/post/2', json => renderPost(json));
// go back to every httpGet call in the application and explicitly pass err along.
httpGet('/post/2', (json, err) => renderPost(json, err));
// renderPost is called from within httpGet with however many arguments it wants
httpGet('/post/2', renderPost);// specific to our current blog
const validArticles = articles =>
articles.filter(article => article !== null && article !== undefined),
// vastly more relevant for future projects
const compact = xs => xs.filter(x => x !== null && x !== undefined);Pure Happiness with Pure Functions
Oh to Be Pure Again
A pure function is a function that, given the same input, will always return the same output and does not have any observable side effect.
// impure
let minimum = 21;
const checkAge = age => age >= minimum;
// pure
const checkAge = (age) => {
const minimum = 21;
return age >= minimum;
};
const immutableState = Object.freeze({ minimum: 21 });const xs = [1,2,3,4,5];
// pure
xs.slice(0,3); // [1,2,3]
xs.slice(0,3); // [1,2,3]
xs.slice(0,3); // [1,2,3]
// impure
xs.splice(0,3); // [1,2,3]
xs.splice(0,3); // [4,5]
xs.splice(0,3); // []Side Effects May Include ...
-
changing the file system
-
inserting a record into a database
-
making an http call
-
mutations
-
printing to the screen / logging
-
obtaining user input
-
querying the DOM
-
accessing system state
A side effect is a change of system state or observable interaction with the outside world that occurs during the calculation of a result.
A function is a special relationship between values: Each of its input values gives back exactly one output value.




The Case for Purity
const squareNumber = memoize(x => x * x);
squareNumber(4); // 16
squareNumber(4); // 16, returns cache for input 4
squareNumber(5); // 25
squareNumber(5); // 25, returns cache for input 5Cacheable
For starters, pure functions can always be cached by input. This is typically done using a technique called memoization:
const memoize = (f) => {
const cache = {};
return (...args) => {
const argStr = JSON.stringify(args);
cache[argStr] = cache[argStr] || f(...args);
return cache[argStr];
};
};Something to note is that you can transform some impure functions into pure ones by delaying evaluation:
const pureHttpCall = memoize((url, params) => () => $.getJSON(url, params));Pure functions are completely self contained. How might this be beneficial? For starters, a function's dependencies are explicit and therefore easier to see and understand - no funny business going on under the hood.
// impure
const signUp = (attrs) => {
const user = saveUser(attrs);
welcomeUser(user);
};
// pure
const signUp = (Db, Email, attrs) => () => {
const user = saveUser(Db, attrs);
welcomeUser(Email, user);
};Testable
Next, we come to realize pure functions make testing much easier. We don't have to mock a "real" payment gateway or setup and assert the state of the world after each test. We simply give the function input and assert output.
Reasonable
Since pure functions don't have side effects, they can only influence the behavior of a program through their output values. Furthermore, since their output values can reliably be calculated using only their input values, pure functions will always preserve referential transparency.
const { Map } = require('immutable');
// Aliases: p = player, a = attacker, t = target
const jobe = Map({ name: 'Jobe', hp: 20, team: 'red' });
const michael = Map({ name: 'Michael', hp: 20, team: 'green' });
const decrementHP = p => p.set('hp', p.get('hp') - 1);
const isSameTeam = (p1, p2) => p1.get('team') === p2.get('team');
const punch = (a, t) => (isSameTeam(a, t) ? t : decrementHP(t));
punch(jobe, michael); // Map({name:'Michael', hp:19, team: 'green'})decrementHP, isSameTeam and punch are all pure and therefore referentially transparent.
//inline the function isSameTeam.
const punch = (a, t) => (a.get('team') === t.get('team') ? t : decrementHP(t));
//Since our data is immutable, we can simply replace the teams with their actual value
onst punch = (a, t) => ('red' === 'green' ? t : decrementHP(t));
//We see that it is false in this case so we can remove the entire if branch
const punch = (a, t) => decrementHP(t);
const punch = (a, t) => t.set('hp', t.get('hp') - 1);This ability to reason about code is terrific for refactoring and understanding code in general. In fact, we used this technique to refactor our flock of seagulls program.
Parallel Code
Finally, and here's the coup de grĂ¢ce, we can run any pure function in parallel since it does not need access to shared memory and it cannot, by definition, have a race condition due to some side effect.
Next...
mostly-adequate-guide
By sddtc
mostly-adequate-guide
- 297