Functional Programming Basics in JavaScript
2:40 PM / Ballroom A
Slide 2
Slide 2 text
Functional Programming Basics
in JavaScript
Jeremy Fairbank
@elpapapollo
Slide 3
Slide 3 text
Understand, apply, and create basic
functional programming tenets and
constructs in JavaScript.
Slide 4
Slide 4 text
Hi, I’m Jeremy
jfairbank
@elpapapollo
pushagency.io
blog.jeremyfairbank.com
simplybuilt.com
Slide 5
Slide 5 text
What is a function?
Slide 6
Slide 6 text
What is a function?
Slide 7
Slide 7 text
What is a function?
Relation that pairs each
element in the domain with
exactly one element in the
range.
Domain Range
Slide 8
Slide 8 text
What is a function?
Slide 9
Slide 9 text
What is a function?
Slide 10
Slide 10 text
What is a function?
Slide 11
Slide 11 text
What is a function?
Domain
Slide 12
Slide 12 text
What is a function?
Domain Range
Slide 13
Slide 13 text
Unique mapping from input to output!
Domain Range
Slide 14
Slide 14 text
Ugh, math?
Slide 15
Slide 15 text
Alonzo Church
Slide 16
Slide 16 text
Alonzo Church
• Church-Turing Thesis
• Lambda Calculus
• Represent computation in
terms of “function abstraction
and application using variable
binding and substitution.”
Slide 17
Slide 17 text
λ-Calculus
Represent computation in terms of “function abstraction and
application using variable binding and substitution.”
Slide 18
Slide 18 text
Represent computation in terms of “function abstraction and
application using variable binding and substitution.”
λ-Calculus
Slide 19
Slide 19 text
Represent computation in terms of “function abstraction and
application using variable binding and substitution.”
λ-Calculus
Bind “x” to function => parameters
Slide 20
Slide 20 text
Represent computation in terms of “function abstraction and
application using variable binding and substitution.”
λ-Calculus
Substitution => apply arguments
Slide 21
Slide 21 text
λ-Calculus
Functions are anonymous.
Slide 22
Slide 22 text
Functions are anonymous.
λ-Calculus
Slide 23
Slide 23 text
Functions are anonymous.
λ-Calculus
X
Slide 24
Slide 24 text
Functions are anonymous.
λ-Calculus
Functions are expressions and units of
computation.
Slide 25
Slide 25 text
λ-Calculus
Functions are single input.
Slide 26
Slide 26 text
Functions are single input.
λ-Calculus
Slide 27
Slide 27 text
Functions are single input.
λ-Calculus
Slide 28
Slide 28 text
Functions are single input.
λ-Calculus
Break down into smaller pieces — currying!
Slide 29
Slide 29 text
That’s nice.
What about functional
programming?
Slide 30
Slide 30 text
λ-Calculus shows the power of
functional computation.
Functional programming brings it to
life in computer science!
Slide 31
Slide 31 text
What is Functional
Programming?
Slide 32
Slide 32 text
– Wikipedia
“In computer science, functional programming
is a programming paradigm — a style of
building the structure and elements of
computer programs — that treats
computation as the evaluation of
mathematical functions and avoids changing-
state and mutable data. It is a declarative
programming paradigm, which means
programming is done with expressions.”
What is Functional Programming?
Slide 33
Slide 33 text
TL;DR
Programming without assignment statements.
Slide 34
Slide 34 text
Key Concepts
• Declarative (vs. Imperative)
• First Class Functions
• Referential Transparency and Purity
• Recursion
• Immutability
• Partial Application and Currying
• Composition
Slide 35
Slide 35 text
Thar be ES2015 ahead!
Slide 36
Slide 36 text
const greeting = 'hello';
let n = 42;
n = 5;
const add = (x, y) => x + y;
const printAndReturn = (string) => {
console.log(string);
return string;
};
function greet(g = 'Hi') {
console.log(g);
}
function print(a, ...args) {
console.log(a, ...args);
}
const [x, ...y] = ['foo', 'bar', 'baz'];
console.log(x); // foo
console.log(y); // ['bar', 'baz']
Slide 37
Slide 37 text
var greeting = 'hello';
const greeting = 'hello';
let n = 42;
n = 5;
const add = (x, y) => x + y;
const printAndReturn = (string) => {
console.log(string);
return string;
};
function greet(g = 'Hi') {
console.log(g);
}
function print(a, ...args) {
console.log(a, ...args);
}
const [x, ...y] = ['foo', 'bar', 'baz'];
console.log(x); // foo
console.log(y); // ['bar', 'baz']
Slide 38
Slide 38 text
var n = 42;
n = 5;
const greeting = 'hello';
let n = 42;
n = 5;
const add = (x, y) => x + y;
const printAndReturn = (string) => {
console.log(string);
return string;
};
function greet(g = 'Hi') {
console.log(g);
}
function print(a, ...args) {
console.log(a, ...args);
}
const [x, ...y] = ['foo', 'bar', 'baz'];
console.log(x); // foo
console.log(y); // ['bar', 'baz']
Slide 39
Slide 39 text
const greeting = 'hello';
let n = 42;
n = 5;
const add = (x, y) => x + y;
const printAndReturn = (string) => {
console.log(string);
return string;
};
function greet(g = 'Hi') {
console.log(g);
}
function print(a, ...args) {
console.log(a, ...args);
}
const [x, ...y] = ['foo', 'bar', 'baz'];
console.log(x); // foo
console.log(y); // ['bar', 'baz']
var add = function(x, y) {
return x + y;
};
var printAndReturn = function(string) {
console.log(string);
return string;
};
Slide 40
Slide 40 text
const greeting = 'hello';
let n = 42;
n = 5;
const add = (x, y) => x + y;
const printAndReturn = (string) => {
console.log(string);
return string;
};
function greet(g = 'Hi') {
console.log(g);
}
function print(a, ...args) {
console.log(a, ...args);
}
const [x, ...y] = ['foo', 'bar', 'baz'];
console.log(x); // foo
console.log(y); // ['bar', 'baz']
function greet() {
var g = arguments[0] === undefined ? 'Hi' : arguments[0];
console.log(g);
}
Slide 41
Slide 41 text
const greeting = 'hello';
let n = 42;
n = 5;
const add = (x, y) => x + y;
const printAndReturn = (string) => {
console.log(string);
return string;
};
function greet(g = 'Hi') {
console.log(g);
}
function print(a, ...args) {
console.log(a, ...args);
}
const [x, ...y] = ['foo', 'bar', 'baz'];
console.log(x); // foo
console.log(y); // ['bar', 'baz']
function print(a) {
var args = [].slice.call(arguments, 1);
console.log.apply(console, [a],concat(args));
}
Slide 42
Slide 42 text
const greeting = 'hello';
let n = 42;
n = 5;
const add = (x, y) => x + y;
const printAndReturn = (string) => {
console.log(string);
return string;
};
function greet(g = 'Hi') {
console.log(g);
}
function print(a, ...args) {
console.log(a, ...args);
}
const [x, ...y] = ['foo', 'bar', 'baz'];
console.log(x); // foo
console.log(y); // ['bar', 'baz']
var _ref = ['foo', 'bar', 'baz'];
var x = _ref[0];
var y = _ref.slice(1);
console.log(x);
console.log(y);
Slide 43
Slide 43 text
Declarative vs. Imperative
Slide 44
Slide 44 text
Imperative
• Most familiar style of programming
• Telling the computer how to accomplish a task
• Algorithms are a series of steps
• Mutable data usually involved
• C, C++, Java
Slide 45
Slide 45 text
Imperative
function doubleNumbers(numbers) {
const doubled = [];
const l = numbers.length;
for (let i = 0; i < l; i++) {
let doubledNumber = numbers[i] * 2;
doubled.push(doubledNumber);
}
return doubled;
}
doubleNumbers([1, 2, 3]);
// [2, 4, 6]
Slide 46
Slide 46 text
Imperative
function doubleNumbers(numbers) {
const doubled = [];
const l = numbers.length;
for (let i = 0; i < l; i++) {
let doubledNumber = numbers[i] * 2;
doubled.push(doubledNumber);
}
return doubled;
}
doubleNumbers([1, 2, 3]);
// [2, 4, 6]
Slide 47
Slide 47 text
Imperative
function doubleNumbers(numbers) {
const doubled = [];
const l = numbers.length;
for (let i = 0; i < l; i++) {
let doubledNumber = numbers[i] * 2;
doubled.push(doubledNumber);
}
return doubled;
}
doubleNumbers([1, 2, 3]);
// [2, 4, 6]
Slide 48
Slide 48 text
Imperative
function doubleNumbers(numbers) {
const doubled = [];
const l = numbers.length;
for (let i = 0; i < l; i++) {
let doubledNumber = numbers[i] * 2;
doubled.push(doubledNumber);
}
return doubled;
}
doubleNumbers([1, 2, 3]);
// [2, 4, 6]
Slide 49
Slide 49 text
Imperative
function doubleNumbers(numbers) {
const doubled = [];
const l = numbers.length;
for (let i = 0; i < l; i++) {
let doubledNumber = numbers[i] * 2;
doubled.push(doubledNumber);
}
return doubled;
}
doubleNumbers([1, 2, 3]);
// [2, 4, 6]
Slide 50
Slide 50 text
Declarative
• Typically associated with functional programming
• Telling the computer what you want, not how to get it
• Algorithms are compositions of functions
• Immutable data (or minimal side effects) preferred
• SQL, HTML, Haskell, DSLs
Slide 51
Slide 51 text
Declarative
function doubleNumber(n) {
return n * 2;
}
function doubleNumbers(numbers) {
return numbers.map(doubleNumber);
}
doubleNumbers([1, 2, 3]);
// [2, 4, 6]
Slide 52
Slide 52 text
First Class Functions
Functions are expressions/values.
Slide 53
Slide 53 text
First Class Functions
Functions are expressions/values.
// Function declaration
function add(x, y) {
return x + y;
}
// Assign to a variable
const addAlias = add;
// Assign an anonymous function expression
const multiply = (x, y) => x * y;
// Functions have properties
console.log(add.name); // 'add'
console.log(addAlias.name); // 'add'
console.log(multiply.name); // ''
Slide 54
Slide 54 text
First Class Functions
Functions are expressions/values.
// Function declaration
function add(x, y) {
return x + y;
}
// Assign to a variable
const addAlias = add;
// Assign an anonymous function expression
const multiply = (x, y) => x * y;
// Functions have properties
console.log(add.name); // 'add'
console.log(addAlias.name); // 'add'
console.log(multiply.name); // ''
Slide 55
Slide 55 text
First Class Functions
Functions are expressions/values.
// Function declaration
function add(x, y) {
return x + y;
}
// Assign to a variable
const addAlias = add;
// Assign an anonymous function expression
const multiply = (x, y) => x * y;
// Functions have properties
console.log(add.name); // 'add'
console.log(addAlias.name); // 'add'
console.log(multiply.name); // ''
Slide 56
Slide 56 text
First Class Functions
Functions are expressions/values.
// Function declaration
function add(x, y) {
return x + y;
}
// Assign to a variable
const addAlias = add;
// Assign an anonymous function expression
const multiply = (x, y) => x * y;
// Functions have properties
console.log(add.name); // 'add'
console.log(addAlias.name); // 'add'
console.log(multiply.name); // ''
Slide 57
Slide 57 text
First Class Functions
Functions as arguments
Slide 58
Slide 58 text
First Class Functions
Functions as arguments
// Passing in anonymous functions
myEventLib.on('update', (data) => console.log(data));
function doubleNumber(n) {
return n * 2;
}
// Earlier example, passing in named function
function doubleNumbers(numbers) {
return numbers.map(doubleNumber);
}
Slide 59
Slide 59 text
First Class Functions
Functions as arguments
// Passing in anonymous functions
myEventLib.on('update', (data) => console.log(data));
function doubleNumber(n) {
return n * 2;
}
// Earlier example, passing in named function
function doubleNumbers(numbers) {
return numbers.map(doubleNumber);
}
Slide 60
Slide 60 text
First Class Functions
Functions as return values
Slide 61
Slide 61 text
First Class Functions
Functions as return values
// Return a new function that adds a number
// to another
function additionFactory(x) {
return (y) => x + y;
}
const add1 = additionFactory(1);
add1(2); // 3
Slide 62
Slide 62 text
First Class Functions
Functions as return values
// Return a new function that adds a number
// to another
function additionFactory(x) {
return (y) => x + y;
}
const add1 = additionFactory(1);
add1(2); // 3
Slide 63
Slide 63 text
First Class Functions
Functions as return values
// Return a new function that adds a number
// to another
function additionFactory(x) {
return (y) => x + y;
}
const add1 = additionFactory(1);
add1(2); // 3
Slide 64
Slide 64 text
Closures
Slide 65
Slide 65 text
Closures
• Allow functions to reference other scopes
• “Close over” variables in higher scopes
• Critical to functional programming paradigms like partial
application
Slide 66
Slide 66 text
Closures
// New function “closes” over x
function additionFactory(x) {
return (y) => x + y;
}
const add1 = additionFactory(1);
add1(2); // 3
Slide 67
Slide 67 text
// New function “closes” over x
function additionFactory(x) {
return (y) => x + y;
}
const add1 = additionFactory(1);
add1(2); // 3
Closures
Same x
Slide 68
Slide 68 text
Closures
let myValue = 'foo';
function closure() {
console.log(myValue);
}
function trickyClosure() {
let myValue = 'bar';
console.log(myValue);
}
function anotherTrickyClosure() {
myValue = 'hello world';
console.log(myValue);
}
closure(); // 'foo'
trickyClosure(); // 'bar'
closure(); // 'foo'
anotherTrickyClosure(); // 'hello world'
closure(); // 'hello world'
trickyClosure(); // 'bar'
Slide 69
Slide 69 text
Closures
let myValue = 'foo';
function closure() {
console.log(myValue);
}
function trickyClosure() {
let myValue = 'bar';
console.log(myValue);
}
function anotherTrickyClosure() {
myValue = 'hello world';
console.log(myValue);
}
closure(); // 'foo'
trickyClosure(); // 'bar'
closure(); // 'foo'
anotherTrickyClosure(); // 'hello world'
closure(); // 'hello world'
trickyClosure(); // 'bar'
Slide 70
Slide 70 text
let myValue = 'foo';
function closure() {
console.log(myValue);
}
function trickyClosure() {
let myValue = 'bar';
console.log(myValue);
}
function anotherTrickyClosure() {
myValue = 'hello world';
console.log(myValue);
}
closure(); // 'foo'
trickyClosure(); // 'bar'
closure(); // 'foo'
anotherTrickyClosure(); // 'hello world'
closure(); // 'hello world'
trickyClosure(); // 'bar'
Closures
Slide 71
Slide 71 text
Closures
let myValue = 'foo';
function closure() {
console.log(myValue);
}
function trickyClosure() {
let myValue = 'bar';
console.log(myValue);
}
function anotherTrickyClosure() {
myValue = 'hello world';
console.log(myValue);
}
closure(); // 'foo'
trickyClosure(); // 'bar'
closure(); // 'foo'
anotherTrickyClosure(); // 'hello world'
closure(); // 'hello world'
trickyClosure(); // 'bar'
Closures
let myValue = 'foo'; // Now 'hello world'
function closure() {
console.log(myValue);
}
function trickyClosure() {
let myValue = 'bar';
console.log(myValue);
}
function anotherTrickyClosure() {
myValue = 'hello world';
console.log(myValue);
}
closure(); // 'foo'
trickyClosure(); // 'bar'
closure(); // 'foo'
anotherTrickyClosure(); // 'hello world'
closure(); // 'hello world'
trickyClosure(); // 'bar'
Slide 74
Slide 74 text
Closures
let myValue = 'foo'; // Now 'hello world'
function closure() {
console.log(myValue);
}
function trickyClosure() {
let myValue = 'bar';
console.log(myValue);
}
function anotherTrickyClosure() {
myValue = 'hello world';
console.log(myValue);
}
closure(); // 'foo'
trickyClosure(); // 'bar'
closure(); // 'foo'
anotherTrickyClosure(); // 'hello world'
closure(); // 'hello world'
trickyClosure(); // 'bar'
Slide 75
Slide 75 text
Referential Transparency
Slide 76
Slide 76 text
Referential Transparency
• Fuzzy term associated with purity
• For a given input, a function always returns the same value
• Replace function calls with return value
• No dependency on state outside function
Slide 77
Slide 77 text
Referential
Transparency
function doubleNumber(n) {
return n * 2;
}
function square(n) {
return n * n;
}
function add6(n) {
return n + 6;
}
// Reduces down to the same value
let value1 = add6(square(doubleNumber(3))); // 42
let value2 = add6(square(6)); // 42
let value3 = add6(36); // 42
let value4 = 42;
// Same value for every call
let otherValue1 = doubleNumber(3) + doubleNumber(3) +
doubleNumber(3) + doubleNumber(3);
let otherValue2 = 6 + 6 + 6 + 6;
Slide 78
Slide 78 text
Referential
Transparency
function doubleNumber(n) {
return n * 2;
}
function square(n) {
return n * n;
}
function add6(n) {
return n + 6;
}
// Reduces down to the same value
let value1 = add6(square(doubleNumber(3))); // 42
let value2 = add6(square(6)); // 42
let value3 = add6(36); // 42
let value4 = 42;
// Same value for every call
let otherValue1 = doubleNumber(3) + doubleNumber(3) +
doubleNumber(3) + doubleNumber(3);
let otherValue2 = 6 + 6 + 6 + 6;
Slide 79
Slide 79 text
Referential
Transparency
function doubleNumber(n) {
return n * 2;
}
function square(n) {
return n * n;
}
function add6(n) {
return n + 6;
}
// Reduces down to the same value
let value1 = add6(square(doubleNumber(3))); // 42
let value2 = add6(square(6)); // 42
let value3 = add6(36); // 42
let value4 = 42;
// Same value for every call
let otherValue1 = doubleNumber(3) + doubleNumber(3) +
doubleNumber(3) + doubleNumber(3);
let otherValue2 = 6 + 6 + 6 + 6;
Slide 80
Slide 80 text
Referential
Transparency
let counter = 0;
function addToCounter1(n) {
counter++;
return n + counter;
}
function addToCounter2(n) {
let counter = 0;
counter++;
return n + counter;
}
// Not referentially transparent
let value1 = addToCounter1(1) + addToCounter1(1); // 5
let value2 = 2 + 3; // Not the same value every call!
// Referentially transparent
let otherValue1 = addToCounter2(1) + addToCounter2(1); // 4
let otherValue2 = 2 + 2;
Slide 81
Slide 81 text
Referential
Transparency
let counter = 0;
function addToCounter1(n) {
counter++;
return n + counter;
}
function addToCounter2(n) {
let counter = 0;
counter++;
return n + counter;
}
// Not referentially transparent
let value1 = addToCounter1(1) + addToCounter1(1); // 5
let value2 = 2 + 3; // Not the same value every call!
// Referentially transparent
let otherValue1 = addToCounter2(1) + addToCounter2(1); // 4
let otherValue2 = 2 + 2;
Slide 82
Slide 82 text
Referential
Transparency
let counter = 0;
function addToCounter1(n) {
counter++;
return n + counter;
}
function addToCounter2(n) {
let counter = 0;
counter++;
return n + counter;
}
// Not referentially transparent
let value1 = addToCounter1(1) + addToCounter1(1); // 5
let value2 = 2 + 3; // Not the same value every call!
// Referentially transparent
let otherValue1 = addToCounter2(1) + addToCounter2(1); // 4
let otherValue2 = 2 + 2;
Slide 83
Slide 83 text
Idempotency
Slide 84
Slide 84 text
Idempotency
• Special case of referential transparency
• Multiple function applications produce the same result as
one application
Slide 85
Slide 85 text
Idempotency
let abs = Math.abs;
function identity(x) {
return x;
}
function add2(n) {
return n + 2;
}
// Idempotent and referentially transparent
abs(abs(abs(abs(-1)))) === abs(-1);
identity(identity(identity(42))) === identity(42);
// Not idempotent, but referentially transparent
add2(add2(add2(1))) !== add2(1); // 7 !== 3
Slide 86
Slide 86 text
Idempotency
let abs = Math.abs;
function identity(x) {
return x;
}
function add2(n) {
return n + 2;
}
// Idempotent and referentially transparent
abs(abs(abs(abs(-1)))) === abs(-1);
identity(identity(identity(42))) === identity(42);
// Not idempotent, but referentially transparent
add2(add2(add2(1))) !== add2(1); // 7 !== 3
Slide 87
Slide 87 text
Idempotency
let abs = Math.abs;
function identity(x) {
return x;
}
function add2(n) {
return n + 2;
}
// Idempotent and referentially transparent
abs(abs(abs(abs(-1)))) === abs(-1);
identity(identity(identity(42))) === identity(42);
// Not idempotent, but referentially transparent
add2(add2(add2(1))) !== add2(1); // 7 !== 3
Slide 88
Slide 88 text
Purity
Slide 89
Slide 89 text
Purity
• “Strict referential transparency”
• No side effects
• No global/shared state
• No impure arguments
• No I/O
Slide 90
Slide 90 text
Pure Functions
function add(x, y) {
return x + y;
}
function capitalize(string) {
return string[0].toUpperCase() +
string.slice(1).toLowerCase();
}
function toTitleCase(string) {
return string
.split(/\s+/)
.map(capitalize)
.join(' ');
}
Slide 91
Slide 91 text
Pure Functions
function add(x, y) {
return x + y;
}
function capitalize(string) {
return string[0].toUpperCase() +
string.slice(1).toLowerCase();
}
function toTitleCase(string) {
return string
.split(/\s+/)
.map(capitalize)
.join(' ');
}
Slide 92
Slide 92 text
Pure Functions
function add(x, y) {
return x + y;
}
function capitalize(string) {
return string[0].toUpperCase() +
string.slice(1).toLowerCase();
}
function toTitleCase(string) {
return string
.split(/\s+/)
.map(capitalize)
.join(' ');
}
Slide 93
Slide 93 text
Pure Functions
function add(x, y) {
return x + y;
}
function capitalize(string) {
return string[0].toUpperCase() +
string.slice(1).toLowerCase();
}
function toTitleCase(string) {
return string
.split(/\s+/)
.map(capitalize)
.join(' ');
}
Slide 94
Slide 94 text
Pure Functions
function add(x, y) {
return x + y;
}
function capitalize(string) {
return string[0].toUpperCase() +
string.slice(1).toLowerCase();
}
function toTitleCase(string) {
return string
.split(/\s+/)
.map(capitalize)
.join(' ');
}
• No side effects.
• String and number
arguments are immutable.
Slide 95
Slide 95 text
Impure Functions
let myName = 'Jeremy';
const myHobbies = ['programming', 'reading', 'playing guitar'];
function getName() {
return myName;
}
function printName(name) {
console.log(name);
}
function add(x, y) {
myName = 'Joe';
return x + y;
}
function hobbiesMapper(hobbies) {
return (fn) => hobbies.map(fn);
}
Slide 96
Slide 96 text
let myName = 'Jeremy';
const myHobbies = ['programming', 'reading', 'playing guitar'];
function getName() {
return myName;
}
function printName(name) {
console.log(name);
}
function add(x, y) {
myName = 'Joe';
return x + y;
}
function hobbiesMapper(hobbies) {
return (fn) => hobbies.map(fn);
}
Impure Functions
Accesses global state
Slide 97
Slide 97 text
let myName = 'Jeremy';
const myHobbies = ['programming', 'reading', 'playing guitar'];
function getName() {
return myName;
}
function printName(name) {
console.log(name);
}
function add(x, y) {
myName = 'Joe';
return x + y;
}
function hobbiesMapper(hobbies) {
return (fn) => hobbies.map(fn);
}
Impure Functions
Prints to stdout
Slide 98
Slide 98 text
let myName = 'Jeremy';
const myHobbies = ['programming', 'reading', 'playing guitar'];
function getName() {
return myName;
}
function printName(name) {
console.log(name);
}
function add(x, y) {
myName = 'Joe';
return x + y;
}
function hobbiesMapper(hobbies) {
return (fn) => hobbies.map(fn);
}
Impure Functions
Modifies global state
Slide 99
Slide 99 text
let myName = 'Jeremy';
const myHobbies = ['programming', 'reading', 'playing guitar'];
function getName() {
return myName;
}
function printName(name) {
console.log(name);
}
function add(x, y) {
myName = 'Joe';
return x + y;
}
function hobbiesMapper(hobbies) {
return (fn) => hobbies.map(fn);
}
Impure Functions
Array may mutate
Slide 100
Slide 100 text
Recursion
Slide 101
Slide 101 text
Recursion
• Defining a solution to a problem in terms of itself
• Solve the problem by solving smaller pieces
• Similar to inductive proofs
• In programming, a function that calls itself
• In FP, imperative looping (e.g. with while or for) is
practically nonexistent, so solve with recursion
Slide 102
Slide 102 text
Recursion: Factorial
Slide 103
Slide 103 text
Recursion: Factorial
function factorial(n) {
let result = 1;
while (n > 1) {
result *= n--;
}
return result;
}
Imperative
Slide 104
Slide 104 text
Recursion: Factorial
function factorial(n) {
let result = 1;
while (n > 1) {
result *= n--;
}
return result;
}
Imperative
function factorial(n) {
if (n < 2) {
return 1;
}
return n * factorial(n - 1);
}
Recursive
Slide 105
Slide 105 text
Recursion: Factorial
function factorial(n) {
if (n < 2) {
return 1;
}
return n * factorial(n - 1);
}
Slide 106
Slide 106 text
Recursion: Factorial
function factorial(n) {
if (n < 2) {
return 1;
}
return n * factorial(n - 1);
}
Base case
Slide 107
Slide 107 text
Recursion: Factorial
function factorial(n) {
if (n < 2) {
return 1;
}
return n * factorial(n - 1);
}
Recursive call
Tail Call Optimization
• Optimize recursive functions to not exhaust max stack
size
• Replace stack frames instead of adding new ones
• The last statement before returning must be recursive call
• Typically rewrite function to include an accumulator that
holds the current result
• (Coming to JavaScript in ES2015!)
Slide 136
Slide 136 text
Tail Call Optimization
Unoptimizable
function factorial(n) {
if (n < 2) {
return 1;
}
return n * factorial(n - 1);
}
Slide 137
Slide 137 text
Tail Call Optimization
Unoptimizable
function factorial(n) {
if (n < 2) {
return 1;
}
return n * factorial(n - 1);
}
1
Slide 138
Slide 138 text
Tail Call Optimization
Unoptimizable
function factorial(n) {
if (n < 2) {
return 1;
}
return n * factorial(n - 1);
}
1
2
Slide 139
Slide 139 text
Tail Call Optimization
Unoptimizable
function factorial(n) {
if (n < 2) {
return 1;
}
return n * factorial(n - 1);
}
1
3
2
Slide 140
Slide 140 text
Optimize Factorial
function factorial(n, accum = 1) {
if (n < 2) {
return accum;
}
return factorial(n - 1, n * accum);
}
Slide 141
Slide 141 text
Optimize Factorial
function factorial(n, accum = 1) {
if (n < 2) {
return accum;
}
return factorial(n - 1, n * accum);
}
Current value
accumulator
Slide 142
Slide 142 text
Optimize Factorial
function factorial(n, accum = 1) {
if (n < 2) {
return accum;
}
return factorial(n - 1, n * accum);
}
Return the accumulator
for base case
Slide 143
Slide 143 text
Optimize Factorial
function factorial(n, accum = 1) {
if (n < 2) {
return accum;
}
return factorial(n - 1, n * accum);
}
Move calculation
inside the call
Slide 144
Slide 144 text
Optimize Factorial
function factorial(n, accum = 1) {
if (n < 2) {
return accum;
}
return factorial(n - 1, n * accum);
}
1
Slide 145
Slide 145 text
Optimize Factorial
function factorial(n, accum = 1) {
if (n < 2) {
return accum;
}
return factorial(n - 1, n * accum);
}
1 2
Slide 146
Slide 146 text
Optimize Factorial
function factorial(n, accum = 1) {
if (n < 2) {
return accum;
}
return factorial(n - 1, n * accum);
}
1
3
2
Slide 147
Slide 147 text
And now?
let value = factorial(100000);
console.log(value);
// Infinity
Performance Revisited
fibonacci(1); // 0 ms
fibonacci(5); // 0 ms
Slide 165
Slide 165 text
Performance Revisited
fibonacci(1); // 0 ms
fibonacci(5); // 0 ms
fibonacci(20); // 0 ms
Slide 166
Slide 166 text
Performance Revisited
fibonacci(1); // 0 ms
fibonacci(5); // 0 ms
fibonacci(20); // 0 ms
fibonacci(30); // 13 ms
Slide 167
Slide 167 text
Performance Revisited
fibonacci(1); // 0 ms
fibonacci(5); // 0 ms
fibonacci(20); // 0 ms
fibonacci(30); // 13 ms
fibonacci(35); // 131 ms
Slide 168
Slide 168 text
Performance Revisited
fibonacci(1); // 0 ms
fibonacci(5); // 0 ms
fibonacci(20); // 0 ms
fibonacci(30); // 13 ms
fibonacci(35); // 131 ms
fibonacci(40); // 1516 ms
Slide 169
Slide 169 text
Performance Revisited
fibonacci(1); // 0 ms
fibonacci(5); // 0 ms
fibonacci(20); // 0 ms
fibonacci(30); // 13 ms
fibonacci(35); // 131 ms
fibonacci(40); // 1516 ms
fibonacci(45); // 16.6 seconds!
Slide 170
Slide 170 text
Performance Revisited
fibonacci(1); // 0 ms
fibonacci(5); // 0 ms
fibonacci(20); // 0 ms
fibonacci(30); // 13 ms
fibonacci(35); // 131 ms
fibonacci(40); // 1516 ms
fibonacci(45); // 16.6 seconds!
fibonacci(100); // Who knows how long
Slide 171
Slide 171 text
Exponential Growth
Runtime (ms)
0
4500
9000
13500
18000
Nth number of Fibonacci Sequence
0 10 20 30 40 50
Slide 172
Slide 172 text
Recursive Call Tree
Slide 173
Slide 173 text
Recursive Call Tree
Computing some of the same values several times!
Slide 174
Slide 174 text
Recursive Call Tree
Computing some of the same values several times!
fibonacci(4) x 2
Slide 175
Slide 175 text
Recursive Call Tree
Computing some of the same values several times!
fibonacci(4) x 2
fibonacci(3) x 3
Slide 176
Slide 176 text
Recursive Call Tree
Computing some of the same values several times!
fibonacci(4) x 2
fibonacci(3) x 3
fibonacci(2) x 5
Slide 177
Slide 177 text
Recursive Call Tree
Computing some of the same values several times!
fibonacci(4) x 2
fibonacci(3) x 3
fibonacci(2) x 5
fibonacci(1) x 3
Slide 178
Slide 178 text
TCO and Dynamic Programming
Slide 179
Slide 179 text
TCO and Dynamic Programming
• Build up the result from the
leaves of the tree
• Avoids recalculating the same
values
• Make recursive calls in the
reverse direction
Slide 180
Slide 180 text
Fibonacci TCO
function fibonacci(n, current = 0, next = 1) {
if (n === 0) {
return current;
}
return fibonacci(n - 1, next, current + next);
}
Slide 181
Slide 181 text
Fibonacci TCO
function fibonacci(n, current = 0, next = 1) {
if (n === 0) {
return current;
}
return fibonacci(n - 1, next, current + next);
}
Two accumulators
Slide 182
Slide 182 text
Fibonacci TCO
function fibonacci(n, current = 0, next = 1) {
if (n === 0) {
return current;
}
return fibonacci(n - 1, next, current + next);
}
Results for next call
Slide 183
Slide 183 text
Fibonacci TCO
function fibonacci(n, current = 0, next = 1) {
if (n === 0) {
return current;
}
return fibonacci(n - 1, next, current + next);
}
Reach the end, so return
whatever was built up
Fibonacci Performance
fibonacci(1); // 0 ms
fibonacci(5); // 0 ms
fibonacci(20); // 0 ms
fibonacci(30); // 0 ms
fibonacci(35); // 0 ms
fibonacci(40); // 0 ms
fibonacci(45); // 0 ms
fibonacci(100); // 0 ms
Slide 187
Slide 187 text
Functional Arrays
Slide 188
Slide 188 text
Functional Arrays
• Common operations like map can be implemented in FP
• Enforce immutability by returning new array
• No looping; use recursion!
Slide 189
Slide 189 text
Array#map
Map values in array to other values in new array
function map(fn, array) {
if (array.length === 0) {
return array;
}
const [head, ...tail] = array;
return [fn(head), ...map(fn, tail)];
}
Slide 190
Slide 190 text
Array#map
Map values in array to other values in new array
function map(fn, array) {
if (array.length === 0) {
return array;
}
const [head, ...tail] = array;
return [fn(head), ...map(fn, tail)];
}
Function first; good
for currying
Slide 191
Slide 191 text
Array#map
Map values in array to other values in new array
function map(fn, array) {
if (array.length === 0) {
return array;
}
const [head, ...tail] = array;
return [fn(head), ...map(fn, tail)];
}
Grab the first element
and remaining elements
Slide 192
Slide 192 text
function map(fn, array) {
if (array.length === 0) {
return array;
}
const [head, ...tail] = array;
return [fn(head), ...map(fn, tail)];
}
Array#map
Map values in array to other values in new array
var head = array[0];
Slide 193
Slide 193 text
function map(fn, array) {
if (array.length === 0) {
return array;
}
const [head, ...tail] = array;
return [fn(head), ...map(fn, tail)];
}
Array#map
Map values in array to other values in new array
var tail = array.slice(1);
Slide 194
Slide 194 text
Array#map
Map values in array to other values in new array
function map(fn, array) {
if (array.length === 0) {
return array;
}
const [head, ...tail] = array;
return [fn(head), ...map(fn, tail)];
}
Return new array with
altered head and mapped
array of remaining elements
Slide 195
Slide 195 text
Array#map
Map values in array to other values in new array
function map(fn, array) {
if (array.length === 0) {
return array;
}
const [head, ...tail] = array;
return [fn(head), ...map(fn, tail)];
}
return [fn(head)].concat(map(fn, tail));
Slide 196
Slide 196 text
Array#map
Map values in array to other values in new array
function map(fn, array) {
if (array.length === 0) {
return array;
}
const [head, ...tail] = array;
return [fn(head), ...map(fn, tail)];
}
Base case: empty array.
Slide 197
Slide 197 text
Array#map
Map values in array to other values in new array
function square(n) {
return n * n;
}
let numbers = [1, 2, 3];
let squaredNumbers = map(square, numbers);
// [1, 4, 9];
Slide 198
Slide 198 text
Partial Application
Slide 199
Slide 199 text
Partial Application
• Assign values to function parameters without evaluating
the function (i.e. “prefill” argument values)
• Produce a new function that takes in any remaining
unassigned arguments
Slide 200
Slide 200 text
Partial
Application
// Before we would use this
function additionFactory(x) {
return (y) => x + y;
}
let add1 = additionFactory(1);
add1(2); // 3
// Now we can write an add function
// and use partial application
function add(x, y) {
return x + y;
}
// Partial application with native `bind` function
add1 = add.bind(null, 1);
add1(2); // 3
// Use a custom-written `partial` function
add1 = partial(add, 1);
add1(2); // 3
Slide 201
Slide 201 text
Partial
Application
// Before we would use this
function additionFactory(x) {
return (y) => x + y;
}
let add1 = additionFactory(1);
add1(2); // 3
// Now we can write an add function
// and use partial application
function add(x, y) {
return x + y;
}
// Partial application with native `bind` function
add1 = add.bind(null, 1);
add1(2); // 3
// Use a custom-written `partial` function
add1 = partial(add, 1);
add1(2); // 3
Slide 202
Slide 202 text
Partial
Application
// Before we would use this
function additionFactory(x) {
return (y) => x + y;
}
let add1 = additionFactory(1);
add1(2); // 3
// Now we can write an add function
// and use partial application
function add(x, y) {
return x + y;
}
// Partial application with native `bind` function
add1 = add.bind(null, 1);
add1(2); // 3
// Use a custom-written `partial` function
add1 = partial(add, 1);
add1(2); // 3
Slide 203
Slide 203 text
Partial
Application
// Before we would use this
function additionFactory(x) {
return (y) => x + y;
}
let add1 = additionFactory(1);
add1(2); // 3
// Now we can write an add function
// and use partial application
function add(x, y) {
return x + y;
}
// Partial application with native `bind` function
add1 = add.bind(null, 1);
add1(2); // 3
// Use a custom-written `partial` function
add1 = partial(add, 1);
add1(2); // 3
Partial Application
function partial(fn, ...args) {
return (...otherArgs) => {
return fn(...args, ...otherArgs);
};
}
Call original function
with all arguments
Slide 209
Slide 209 text
Partial Application: Why?
• Build up functions from smaller pieces
• Remove duplication
• Generalize function abstraction (i.e. no
additionFactory type functions)
Currying
• Similar to partial application
• Bakes partial application into a function
• Successive invocations of function with arguments returns
a new function with those arguments assigned
• Keep returning a new function until all arguments “filled,”
and then invoke the actual function with all of those
arguments (sounds recursive, huh?)
Slide 213
Slide 213 text
Currying
const add = curry((x, y, z) => x + y + z);
// All arguments supplied, normal invocation.
add(1, 2, 3); // 6
// Supply arguments one at a time. Final argument
// induces invocation.
add(1)(2)(3); // 6
// Equivalent to previous example.
let add1 = add(1);
let add3 = add1(2);
add3(3); // 6
// Supply more than one argument at a time.
add(1, 2)(3); // 6
add(1)(2, 3); // 6
Slide 214
Slide 214 text
Currying
const add = curry((x, y, z) => x + y + z);
// All arguments supplied, normal invocation.
add(1, 2, 3); // 6
// Supply arguments one at a time. Final argument
// induces invocation.
add(1)(2)(3); // 6
// Equivalent to previous example.
let add1 = add(1);
let add3 = add1(2);
add3(3); // 6
// Supply more than one argument at a time.
add(1, 2)(3); // 6
add(1)(2, 3); // 6
Slide 215
Slide 215 text
Currying
const add = curry((x, y, z) => x + y + z);
// All arguments supplied, normal invocation.
add(1, 2, 3); // 6
// Supply arguments one at a time. Final argument
// induces invocation.
add(1)(2)(3); // 6
// Equivalent to previous example.
let add1 = add(1);
let add3 = add1(2);
add3(3); // 6
// Supply more than one argument at a time.
add(1, 2)(3); // 6
add(1)(2, 3); // 6
Slide 216
Slide 216 text
Currying
const add = curry((x, y, z) => x + y + z);
// All arguments supplied, normal invocation.
add(1, 2, 3); // 6
// Supply arguments one at a time. Final argument
// induces invocation.
add(1)(2)(3); // 6
// Equivalent to previous example.
let add1 = add(1);
let add3 = add1(2);
add3(3); // 6
// Supply more than one argument at a time.
add(1, 2)(3); // 6
add(1)(2, 3); // 6
Slide 217
Slide 217 text
Currying
const add = curry((x, y, z) => x + y + z);
// All arguments supplied, normal invocation.
add(1, 2, 3); // 6
// Supply arguments one at a time. Final argument
// induces invocation.
add(1)(2)(3); // 6
// Equivalent to previous example.
let add1 = add(1);
let add3 = add1(2);
add3(3); // 6
// Supply more than one argument at a time.
add(1, 2)(3); // 6
add(1)(2, 3); // 6
Slide 218
Slide 218 text
Currying
const add = curry((x, y, z) => x + y + z);
// All arguments supplied, normal invocation.
add(1, 2, 3); // 6
// Supply arguments one at a time. Final argument
// induces invocation.
add(1)(2)(3); // 6
// Equivalent to previous example.
let add1 = add(1);
let add3 = add1(2);
add3(3); // 6
// Supply more than one argument at a time.
add(1, 2)(3); // 6
add(1)(2, 3); // 6
Slide 219
Slide 219 text
Currying
function curry(fn, len = fn.length) {
return (...args) => {
if (args.length >= len) {
return fn(...args);
}
return curry(
partial(fn, ...args),
len - args.length
);
};
}
Slide 220
Slide 220 text
Currying
function curry(fn, len = fn.length) {
return (...args) => {
if (args.length >= len) {
return fn(...args);
}
return curry(
partial(fn, ...args),
len - args.length
);
};
}
Knowing arity is
important!
Slide 221
Slide 221 text
Currying
function curry(fn, len = fn.length) {
return (...args) => {
if (args.length >= len) {
return fn(...args);
}
return curry(
partial(fn, ...args),
len - args.length
);
};
}
Arguments to apply
(or invoke)
Slide 222
Slide 222 text
Currying
function curry(fn, len = fn.length) {
return (...args) => {
if (args.length >= len) {
return fn(...args);
}
return curry(
partial(fn, ...args),
len - args.length
);
};
}
Recursive call if not
all arguments supplied
Slide 223
Slide 223 text
Currying
function curry(fn, len = fn.length) {
return (...args) => {
if (args.length >= len) {
return fn(...args);
}
return curry(
partial(fn, ...args),
len - args.length
);
};
}
Curry the partially
applied function
Slide 224
Slide 224 text
Currying
function curry(fn, len = fn.length) {
return (...args) => {
if (args.length >= len) {
return fn(...args);
}
return curry(
partial(fn, ...args),
len - args.length
);
};
}
Arity decreases
Slide 225
Slide 225 text
Currying
function curry(fn, len = fn.length) {
return (...args) => {
if (args.length >= len) {
return fn(...args);
}
return curry(
partial(fn, ...args),
len - args.length
);
};
}
Base case: all
arguments
supplied
Slide 226
Slide 226 text
Currying: Why?
let curriedMap = curry(map);
let multiply = curry((x, y) => x * y);
let doubleNumbers = curriedMap(multiply(2));
let numbers = [1, 2, 3];
console.log(doubleNumbers(numbers));
// [2, 4, 6]
Slide 227
Slide 227 text
Function Composition
Slide 228
Slide 228 text
Function Composition
Slide 229
Slide 229 text
Function Composition
• Compose functions together
to form new functions
• Pipe function output to next
function
• Helps enforce modularity/
SOC by keeping functions
small
Slide 230
Slide 230 text
Function Composition
let multiply = curry((x, y) => x * y);
let multiply2 = multiply(2);
let multiply3 = multiply(3);
let multiply6 = compose(multiply2, multiply3);
2 * 3 * 6 === multiply6(6); // 36
let add = curry((x, y) => x + y);
let subtract = curry((y, x) => -y + x);
let add5 = compose(add(6), add(1), subtract(2));
3 - 2 + 6 + 1 === add5(3); // 8
Slide 231
Slide 231 text
Function Composition
let multiply = curry((x, y) => x * y);
let multiply2 = multiply(2);
let multiply3 = multiply(3);
let multiply6 = compose(multiply2, multiply3);
2 * 3 * 6 === multiply6(6); // 36
let add = curry((x, y) => x + y);
let subtract = curry((y, x) => -y + x);
let add5 = compose(add(6), add(1), subtract(2));
3 - 2 + 6 + 1 === add5(3); // 8
Slide 232
Slide 232 text
Function Composition
let multiply = curry((x, y) => x * y);
let multiply2 = multiply(2);
let multiply3 = multiply(3);
let multiply6 = compose(multiply2, multiply3);
2 * 3 * 6 === multiply6(6); // 36
let add = curry((x, y) => x + y);
let subtract = curry((y, x) => -y + x);
let add5 = compose(add(6), add(1), subtract(2));
3 - 2 + 6 + 1 === add5(3); // 8
Slide 233
Slide 233 text
Function Composition
function compose(...fns) {
return (...args) => {
const result = fns.reduceRight((memo, fn) => {
return [fn(...memo)];
}, args);
return result[0];
};
}
function compose(f, g) {
return (...args) => f(g(...args));
}
Slide 234
Slide 234 text
Function Composition
function compose(...fns) {
return (...args) => {
const result = fns.reduceRight((memo, fn) => {
return [fn(...memo)];
}, args);
return result[0];
};
}
function compose(f, g) {
return (...args) => f(g(...args));
}
Slide 235
Slide 235 text
Function Composition
function compose(...fns) {
return (...args) => {
const result = fns.reduceRight((memo, fn) => {
return [fn(...memo)];
}, args);
return result[0];
};
}
function compose(f, g) {
return (...args) => f(g(...args));
}
Slide 236
Slide 236 text
Function Composition
function compose(...fns) {
return (...args) => {
const result = fns.reduceRight((memo, fn) => {
return [fn(...memo)];
}, args);
return result[0];
};
}
function compose(f, g) {
return (...args) => f(g(...args));
}
Slide 237
Slide 237 text
Function Composition
function compose(...fns) {
return (...args) => {
const result = fns.reduceRight((memo, fn) => {
return [fn(...memo)];
}, args);
return result[0];
};
}
function compose(f, g) {
return (...args) => f(g(...args));
}
Slide 238
Slide 238 text
Slide 239
Slide 239 text
But why Functional
Programming?
Slide 240
Slide 240 text
Why FP
• Concise, elegant solutions to problems
• Modularity and composition
• Eliminate data races with immutability
• Unit tests are simpler — no checking for state changes