Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Speaker Deck
PRO
Sign in
Sign up
for free
Scenic City Summit: Functional Programming Basics in ES6
Jeremy Fairbank
August 12, 2016
Programming
6
910
Scenic City Summit: Functional Programming Basics in ES6
Jeremy Fairbank
August 12, 2016
Tweet
Share
More Decks by Jeremy Fairbank
See All by Jeremy Fairbank
jfairbank
0
71
jfairbank
1
75
jfairbank
1
210
jfairbank
0
110
jfairbank
0
120
jfairbank
0
23
jfairbank
2
190
jfairbank
1
89
jfairbank
2
620
Other Decks in Programming
See All in Programming
malvinstn
1
670
ajstarks
2
550
mfunaki
1
490
line_developers_tw
0
560
yaamaa
0
460
supikiti
4
1.4k
wasabeef
1
580
zsmb
2
110
azdaroth
0
100
dulltz
0
540
xrdnk
0
130
brunopulis
1
100
Featured
See All Featured
scottboms
251
11k
phodgson
87
3.9k
chrislema
231
16k
shpigford
368
42k
davidbonilla
70
3.5k
jnunemaker
PRO
40
4.6k
dotmariusz
94
5.1k
geeforr
332
29k
imathis
478
150k
brettharned
93
3k
aarron
257
36k
mongodb
23
3.8k
Transcript
FUNCTIONAL PROGRAMMING Jeremy Fairbank blog.jeremyfairbank.com @elpapapollo / jfairbank BASICS IN
ES6
sigient.com Your website, SimplyBuilt. simplybuilt.com
None
¯\_(ϑ)_/¯ WHAT IS FUNCTIONAL PROGRAMMING?
¯\_(ϑ)_/¯ WHY FUNCTIONAL PROGRAMMING?
None
Domain to Range
None
None
Domain Range
CALCULUS
PRINCIPLES
PURE & DECLARATIVE PREDICTABLE
IMMUTABLE STATE SAFE
FIRST CLASS STATE TRANSPARENT
COMPOSABLE FIRST CLASS CLOSURES MODULAR
ES2015 (ES6)
All good let age = 28; age = 29; const
name = 'Jeremy'; name = 'Jet'; Syntax error
const add = (x, y) => { return x +
y; }; const identity = x => x;
const add = (x, y) => { return x +
y; }; const identity = x => x;
const array = (...elements) => { return elements; }; array(1,
2, 3); // [1, 2, 3] const log = (...args) => { console.log(...args); }; log('hello', 'scenic city summit'); // hello scenic city summit
const array = (...elements) => { return elements; }; array(1,
2, 3); // [1, 2, 3] const log = (...args) => { console.log(...args); }; log('hello', 'scenic city summit'); // hello scenic city summit
const langs = ['JavaScript', 'Ruby', 'Haskell']; const [js, ...rest] =
langs; js === 'JavaScript'; rest[0] === 'Ruby'; rest[1] === 'Haskell'; const head = ([x]) => x; head([1, 2, 3]) === 1;
const langs = ['JavaScript', 'Ruby', 'Haskell']; const [js, ...rest] =
langs; js === 'JavaScript'; rest[0] === 'Ruby'; rest[1] === 'Haskell'; const head = ([x]) => x; head([1, 2, 3]) === 1;
const greet = (name, greeting = 'Hi') => { console.log(greeting,
name); }; greet('Scenic City Summit', 'Hello'); // Hello Scenic City Summit greet('Chattanooga'); // Hi Chattanooga
Object.assign( {}, { hello: 'Chattanooga' }, { hi: 'Scenic City
Summit' } ); // { // hello: 'Chattanooga', // hi: 'Scenic City Summit' // }
class Point { constructor(x, y) { this.x = x; this.y
= y; } moveBy(dx, dy) { this.x += dx; this.y += dy; } } function Point(x, y) { this.x = x; this.y = y; } Point.prototype.moveBy = function(dx, dy) { this.x += dx; this.y += dy; };
PURE
const add = (x, y) => x + y; add(2,
3) === 5; add(2, 3) === 5; add(2, 3) === 5;
const add = (x, y) => x + y; add(2,
3) === 5; add(2, 3) === 5; add(2, 3) === 5; Referentially transparent
× let name = 'Jeremy'; const getName = () =>
name; const setName = (newName) => { name = newName; }; const printUpperName = () => { console.log(name.toUpperCase()); };
describe('api', () => { beforeEach(() => mockConsoleLog()); afterEach(() => restoreConsoleLog());
it('sets and prints the name', () => { printUpperName(); expect(console.log).calledWith('JEREMY'); setName('Jet'); printUpperName(); expect(console.log).calledWith('JET'); }); }); ×
HIDDEN STATE IS UNCERTAIN STATE
const upperName = (name) => name.toUpperCase(); describe('api', () => {
it('returns an uppercase name', () => { expect(upperName('Jeremy')).to.equal('JEREMY'); expect(upperName('Jet')).to.equal('JET'); }); }); ✓
HOW TO ACHIEVE THE RESULT IMPERATIVE
function doubleNumbers(numbers) { const doubled = []; const l =
numbers.length; for (let i = 0; i < l; i++) { doubled.push(numbers[i] * 2); } return doubled; } doubleNumbers([1, 2, 3]); // [2, 4, 6]
DECLARE WHAT THE DESIRED RESULT IS DECLARATIVE
function doubleNumbers(numbers) { return numbers.map(n => n * 2); }
doubleNumbers([1, 2, 3]); // [2, 4, 6]
1 2 3 numbers.map(n => n * 2) 2 4
6 Domain Range
CREATE STATE, DON’T MUTATE IT IMMUTABLE
const hobbies = [ 'programming', 'reading', 'music' ]; const firstTwo
= hobbies.splice(0, 2); console.log(firstTwo); // ['programming', 'reading'] console.log(hobbies); // ['music']
const hobbies = [ 'programming', 'reading', 'music' ]; const firstTwo
= hobbies.splice(0, 2); console.log(firstTwo); // ['programming', 'reading'] console.log(hobbies); // ['music'] Slight typo + mutability
Object.freeze Immutable.js
const hobbies = Object.freeze([ 'programming', 'reading', 'music' ]); const firstTwo
= hobbies.splice(0, 2); // TypeError
FREE YOUR STATE
class Point { constructor(x, y) { this.x = x; this.y
= y; } moveBy(dx, dy) { this.x += dx; this.y += dy; } } const point = new Point(0, 0); point.moveBy(5, 5); point.moveBy(-2, 2); console.log([point.x, point.y]); // [3, 7] ×
const createPoint = (x, y) => Object.freeze([x, y]); const movePointBy
= ([x, y], dx, dy) => { return Object.freeze([x + dx, y + dy]); }; let point = createPoint(0, 0); point = movePointBy(point, 5, 5); point = movePointBy(point, -2, 2); console.log(point); // [3, 7] ✓ (*or object-oriented without mutation)
PROS SAFETY FREE UNDO/REDO LOGS — REDUX EXPLICIT FLOW OF
DATA LESS MEMORY USAGE* CONCURRENCY SAFETY* *In certain cases
CONS VERBOSE MORE OBJECT CREATION* MORE GARBAGE COLLECTION* MORE MEMORY
USAGE* *Alleviated with libs like Immutable.js
FIRST CLASS FUNCTIONS
const multiply = (x, y) => x * y; function
add(x, y) { return x + y; } const addAlias = add; const evens = [1, 2, 3].map(n => n * 2);
const multiply = (x, y) => x * y; function
add(x, y) { return x + y; } const addAlias = add; const evens = [1, 2, 3].map(n => n * 2);
const multiply = (x, y) => x * y; function
add(x, y) { return x + y; } const addAlias = add; const evens = [1, 2, 3].map(n => n * 2);
const multiply = (x, y) => x * y; function
add(x, y) { return x + y; } const addAlias = add; const evens = [1, 2, 3].map(n => n * 2);
ENCAPSULATION CLOSURES
const createAdder = (x) => { return (y) => x
+ y; }; const add3 = createAdder(3); add3(2) === 5; add3(3) === 6;
const createAdder = (x) => { return (y) => x
+ y; }; const add3 = createAdder(3); add3(2) === 5; add3(3) === 6;
const request = (options) => { return fetch(options.url, options) .then(resp
=> resp.json()); }; const usersPromise = request({ url: '/users', headers: { 'X-Custom': 'mykey' } }); const tasksPromise = request({ url: '/tasks', headers: { 'X-Custom': 'mykey' } });
const request = (options) => { return fetch(options.url, options) .then(resp
=> resp.json()); }; const usersPromise = request({ url: '/users', headers: { 'X-Custom': 'mykey' } }); const tasksPromise = request({ url: '/tasks', headers: { 'X-Custom': 'mykey' } }); Repetitive
const createRequester = (options) => { return (otherOptions) => {
return request(Object.assign( {}, options, otherOptions )); }; }; const customRequest = createRequester({ headers: { 'X-Custom': 'mykey' } }); const usersPromise = customRequest({ url: '/users' }); const tasksPromise = customRequest({ url: '/tasks' });
const createRequester = (options) => { return (otherOptions) => {
return request(Object.assign( {}, options, otherOptions )); }; }; const customRequest = createRequester({ headers: { 'X-Custom': 'mykey' } }); const usersPromise = customRequest({ url: '/users' }); const tasksPromise = customRequest({ url: '/tasks' });
FOUNDATION FOR HIGHER ORDER PATTERNS FIRST CLASS CLOSURES
PARTIAL APPLICATION
const createAdder = (x) => { return (y) => x
+ y; }; const createRequester = (options) => { return (otherOptions) => { return request(Object.assign( {}, options, otherOptions )); }; }; RECALL
const add = (x, y) => x + y; const
add3 = partial(add, 3); add3(2) === 5;
const request = (defaults, options) => { options = Object.assign({},
defaults, options); return fetch(options.url, options) .then(resp => resp.json()); }; const customRequest = partial(request, { headers: { 'X-Custom': 'mykey' } }); const usersPromise = customRequest({ url: '/users' }); const tasksPromise = customRequest({ url: '/tasks' });
const partialFromBind = (fn, ...args) => { return fn.bind(null, ...args);
}; const partial = (fn, ...args) => { return (...otherArgs) => { return fn(...args, ...otherArgs) }; };
const partialFromBind = (fn, ...args) => { return fn.bind(null, ...args);
}; const partial = (fn, ...args) => { return (...otherArgs) => { return fn(...args, ...otherArgs) }; };
const partialFromBind = (fn, ...args) => { return fn.bind(null, ...args);
}; const partial = (fn, ...args) => { return (...otherArgs) => { return fn(...args, ...otherArgs) }; };
const partialFromBind = (fn, ...args) => { return fn.bind(null, ...args);
}; const partial = (fn, ...args) => { return (...otherArgs) => { return fn(...args, ...otherArgs) }; };
const partialFromBind = (fn, ...args) => { return fn.bind(null, ...args);
}; const partial = (fn, ...args) => { return (...otherArgs) => { return fn(...args, ...otherArgs) }; };
const partialFromBind = (fn, ...args) => { return fn.bind(null, ...args);
}; const partial = (fn, ...args) => { return (...otherArgs) => { return fn(...args, ...otherArgs) }; };
CURRYING
const add3 = add(3); add3(2) === 5; const customRequest =
request({ headers: { 'X-Custom': 'mykey' } }); const usersPromise = customRequest({ url: '/users' }); const tasksPromise = customRequest({ url: '/tasks' });
const add3 = add(3); add3(2) === 5; const customRequest =
request({ headers: { 'X-Custom': 'mykey' } }); const usersPromise = customRequest({ url: '/users' }); const tasksPromise = customRequest({ url: '/tasks' });
const add = x => y => x + y;
function add(x) { return function(y) { return x + y; }; }
const add = x => y => x + y;
function add(x) { return function(y) { return x + y; }; }
const add = x => y => x + y;
function add(x) { return function(y) { return x + y; }; }
const request = defaults => options => { options =
Object.assign( {}, defaults, options ); return fetch(options.url, options) .then(resp => resp.json()); };
const request = defaults => options => { options =
Object.assign( {}, defaults, options ); return fetch(options.url, options) .then(resp => resp.json()); };
PIECING IT TOGETHER
const map = fn => array => array.map(fn); const multiply
= x => y => x * y; const pluck = key => object => object[key]; const discount = multiply(0.98); const tax = multiply(1.0925); const customRequest = request({ headers: { 'X-Custom': 'mykey' } }); customRequest({ url: '/cart/items' }) .then(map(pluck('price'))) .then(map(discount)) .then(map(tax));
const map = fn => array => array.map(fn); const multiply
= x => y => x * y; const pluck = key => object => object[key]; const discount = multiply(0.98); const tax = multiply(1.0925); const customRequest = request({ headers: { 'X-Custom': 'mykey' } }); customRequest({ url: '/cart/items' }) .then(map(pluck('price'))) .then(map(discount)) .then(map(tax));
const map = fn => array => array.map(fn); const multiply
= x => y => x * y; const pluck = key => object => object[key]; const discount = multiply(0.98); const tax = multiply(1.0925); const customRequest = request({ headers: { 'X-Custom': 'mykey' } }); customRequest({ url: '/cart/items' }) .then(map(pluck('price'))) .then(map(discount)) .then(map(tax));
const map = fn => array => array.map(fn); const multiply
= x => y => x * y; const pluck = key => object => object[key]; const discount = multiply(0.98); const tax = multiply(1.0925); const customRequest = request({ headers: { 'X-Custom': 'mykey' } }); customRequest({ url: '/cart/items' }) .then(map(pluck('price'))) .then(map(discount)) .then(map(tax));
const map = fn => array => array.map(fn); const multiply
= x => y => x * y; const pluck = key => object => object[key]; const discount = multiply(0.98); const tax = multiply(1.0925); const customRequest = request({ headers: { 'X-Custom': 'mykey' } }); customRequest({ url: '/cart/items' }) .then(map(pluck('price'))) .then(map(discount)) .then(map(tax));
const map = fn => array => array.map(fn); const multiply
= x => y => x * y; const pluck = key => object => object[key]; const discount = multiply(0.98); const tax = multiply(1.0925); const customRequest = request({ headers: { 'X-Custom': 'mykey' } }); customRequest({ url: '/cart/items' }) .then(map(pluck('price'))) .then(map(discount)) .then(map(tax));
const map = fn => array => array.map(fn); const multiply
= x => y => x * y; const pluck = key => object => object[key]; const discount = multiply(0.98); const tax = multiply(1.0925); const customRequest = request({ headers: { 'X-Custom': 'mykey' } }); customRequest({ url: '/cart/items' }) .then(map(pluck('price'))) .then(map(discount)) .then(map(tax));
customRequest({ url: '/cart/items' }) .then(map(pluck('price'))) .then(map(discount)) .then(map(tax)); [ { price:
5 }, { price: 10 }, { price: 3 } ]
[ { price: 5 }, { price: 10 }, {
price: 3 } ] map(pluck('price')) [ 5, 10, 3 ] item.price
[ 5, 10, 3 ] map(discount) [ 4.9, 9.8, 2.94
] price * 0.98
[ 5.35, 10.71, 3.21 ] map(tax) [ 4.9, 9.8, 2.94
] price * 1.0925
COMPOSING CLOSURES
None
const processWord = compose(hyphenate, reverse, toUpperCase); const words = [
'hello', 'functional', 'programming' ]; const newWords = words.map(processWord); console.log(newWords); // ['OL-LEH, 'LANOI-TCNUF', 'GNIMM-ARGORP']
const processWord = compose(hyphenate, reverse, toUpperCase); const words = [
'hello', 'functional', 'programming' ]; const newWords = words.map(processWord); console.log(newWords); // ['OL-LEH, 'LANOI-TCNUF', 'GNIMM-ARGORP']
const processWord = compose(hyphenate, reverse, toUpperCase); const processWordExplicit = (word)
=> { return hyphenate(reverse(toUpperCase(word))); };
customRequest({ url: '/cart/items' }) .then(map(pluck('price'))) .then(map(discount)) .then(map(tax)); RETURNING TO PRICES
EXAMPLE
customRequest({ url: '/cart/items' }) .then(map(pluck('price'))) .then(map(discount)) .then(map(tax)); Triple iteration RETURNING
TO PRICES EXAMPLE
customRequest({ url: '/cart/items' }) .then(map( compose( tax, discount, pluck('price') )
)); Single iteration
const compose = (...fns) => (...args) => { if (fns.length
=== 0) { return args[0]; } const last = fns[fns.length - 1]; const rest = fns.slice(0, -1); return rest.reduceRight((memo, fn) => { return fn(memo); }, last(...args)); };
const compose = (...fns) => (...args) => { if (fns.length
=== 0) { return args[0]; } const last = fns[fns.length - 1]; const rest = fns.slice(0, -1); return rest.reduceRight((memo, fn) => { return fn(memo); }, last(...args)); };
const compose = (...fns) => (...args) => { if (fns.length
=== 0) { return args[0]; } const last = fns[fns.length - 1]; const rest = fns.slice(0, -1); return rest.reduceRight((memo, fn) => { return fn(memo); }, last(...args)); };
const compose = (...fns) => (...args) => { if (fns.length
=== 0) { return args[0]; } const last = fns[fns.length - 1]; const rest = fns.slice(0, -1); return rest.reduceRight((memo, fn) => { return fn(memo); }, last(...args)); };
RECURSION SOLVE A PROBLEM IN TERMS OF ITSELF
FACTORIAL
const factorial = (n) => { let result = 1;
while (n > 1) { result *= n; n--; } return result; };
const factorial = (n) => { if (n < 2)
{ return 1; } return n * factorial(n - 1); };
const factorial = (n) => { if (n < 2)
{ return 1; } return n * factorial(n - 1); }; Recursive call
const factorial = (n) => { if (n < 2)
{ return 1; } return n * factorial(n - 1); }; Base case
factorial(4);
factorial(4); 4 * factorial(3);
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2);
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2); 4
* 3 * 2 * factorial(1);
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2); 4
* 3 * 2 * factorial(1); 4 * 3 * 2 * 1;
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2); 4
* 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2;
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2); 4
* 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6;
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2); 4
* 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24;
STEPS Find the recurrence (n × n-1 × n-2 ×
… 1) Find the base case (n < 2)
PERFORMANCE RECURSION
const value = factorial(100000); console.log(value); // ??? WHAT IS THE
RESULT?
const value = factorial(100000); console.log(value); // ??? WHAT IS THE
RESULT? RangeError: Maximum call stack size exceeded
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2); 4
* 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; <main> Call Stack
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2); 4
* 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; <main> factorial(4) Call Stack
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2); 4
* 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; <main> factorial(4) factorial(3) Call Stack
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2); 4
* 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; <main> factorial(4) factorial(3) factorial(2) Call Stack
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2); 4
* 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; <main> factorial(4) factorial(3) factorial(2) factorial(1) Call Stack
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2); 4
* 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; <main> factorial(4) factorial(3) factorial(2) Call Stack
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2); 4
* 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; <main> factorial(4) factorial(3) Call Stack
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2); 4
* 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; <main> factorial(4) Call Stack
factorial(4); 4 * factorial(3); 4 * 3 * factorial(2); 4
* 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; <main> Call Stack
const value = factorial(100000); console.log(value); // ??? 100,000 calls =
100,000 stack frames 1 stack frame ≈ 48B Max stack usage ≈ 1MB 100,000 x 48 / 1024 / 1024 = 4.58MB > 1MB
IN ES2015! TAIL CALL OPTIMIZATION
const factorial = (n) => { if (n < 2)
{ return 1; } return n * factorial(n - 1); }; UNOPTIMIZABLE
const factorial = (n) => { if (n < 2)
{ return 1; } return n * factorial(n - 1); }; 1 UNOPTIMIZABLE
const factorial = (n) => { if (n < 2)
{ return 1; } return n * factorial(n - 1); }; 1 UNOPTIMIZABLE 2
const factorial = (n) => { if (n < 2)
{ return 1; } return n * factorial(n - 1); }; 1 UNOPTIMIZABLE 2 3
const factorial = (n, accum = 1) => { if
(n < 2) { return accum; } return factorial(n - 1, n * accum); }; OPTIMIZABLE
const factorial = (n, accum = 1) => { if
(n < 2) { return accum; } return factorial(n - 1, n * accum); }; OPTIMIZABLE
const factorial = (n, accum = 1) => { if
(n < 2) { return accum; } return factorial(n - 1, n * accum); }; OPTIMIZABLE
const factorial = (n, accum = 1) => { if
(n < 2) { return accum; } return factorial(n - 1, n * accum); }; OPTIMIZABLE
const factorial = (n, accum = 1) => { if
(n < 2) { return accum; } return factorial(n - 1, n * accum); }; 1 OPTIMIZABLE
const factorial = (n, accum = 1) => { if
(n < 2) { return accum; } return factorial(n - 1, n * accum); }; 1 2 OPTIMIZABLE
const factorial = (n, accum = 1) => { if
(n < 2) { return accum; } return factorial(n - 1, n * accum); }; 1 2 3 OPTIMIZABLE
const value = factorial(100000); console.log(value); // Infinity
factorial(4 /*, 1 */); factorial(3, 4); factorial(2, 12); factorial(1, 24);
24; <main> Call Stack
factorial(4 /*, 1 */); factorial(3, 4); factorial(2, 12); factorial(1, 24);
24; <main> factorial(4, 1) Call Stack
factorial(4 /*, 1 */); factorial(3, 4); factorial(2, 12); factorial(1, 24);
24; <main> factorial(3, 4) Call Stack
factorial(4 /*, 1 */); factorial(3, 4); factorial(2, 12); factorial(1, 24);
24; <main> factorial(2, 12) Call Stack
factorial(4 /*, 1 */); factorial(3, 4); factorial(2, 12); factorial(1, 24);
24; <main> factorial(1, 24) Call Stack
factorial(4 /*, 1 */); factorial(3, 4); factorial(2, 12); factorial(1, 24);
24; <main> Call Stack
RECAP PREDICTABLE SAFE TRANSPARENT MODULAR
RESOURCES
drboolean.gitbooks.io/mostly-adequate-guide Brian Lonsdorf
ES6/7/LATER babeljs.io
LANGUAGES Elm (elm-lang.org) Clojurescript (github.com/clojure/clojurescript) Purescript (purescript.org)
LIBRARIES Lodash (lodash.com) Ramda (ramdajs.com) Rx (reactivex.io) Bacon.js (baconjs.github.io) Immutable.js
(facebook.github.io/immutable-js)
“MV*” React (facebook.github.io/react) Redux (redux.js.org)
THANKS! Jeremy Fairbank blog.jeremyfairbank.com @elpapapollo / jfairbank Code: github.com/jfairbank/fp-basics-in-es6