ConnectJS Functional Programming Basics in ES6

ConnectJS Functional Programming Basics in ES6

94bd558238b69c45d3d3e15797ae94f7?s=128

Jeremy Fairbank

October 16, 2015
Tweet

Transcript

  1. Functional Programming Basics in ES6 Jeremy Fairbank jeremyfairbank.com @elpapapollo

  2. Hi, I’m Jeremy jfairbank @elpapapollo pushagency.io blog.jeremyfairbank.com simplybuilt.com

  3. What is a function?

  4. What is a function?

  5. What is a function? Relation that pairs each element in

    the domain with exactly one element in the range. Domain Range
  6. What is a function?

  7. What is a function?

  8. What is a function?

  9. What is a function? Domain

  10. What is a function? Domain Range

  11. Unique mapping from input to output! Domain Range

  12. Ugh, math?

  13. Alonzo Church

  14. Alonzo Church • Church-Turing Thesis • Lambda Calculus • Represent

    computation in terms of “function abstraction and application using variable binding and substitution.”
  15. λ-Calculus Represent computation in terms of “function abstraction and application

    using variable binding and substitution.”
  16. Represent computation in terms of “function abstraction and application using

    variable binding and substitution.” λ-Calculus
  17. Represent computation in terms of “function abstraction and application using

    variable binding and substitution.” λ-Calculus Bind “x” to function => parameters
  18. Represent computation in terms of “function abstraction and application using

    variable binding and substitution.” λ-Calculus Substitution => apply arguments
  19. λ-Calculus Functions are anonymous.

  20. Functions are anonymous. λ-Calculus

  21. Functions are anonymous. λ-Calculus X

  22. Functions are anonymous. λ-Calculus Functions are expressions and units of

    computation.
  23. λ-Calculus Functions are single input.

  24. Functions are single input. λ-Calculus

  25. Functions are single input. λ-Calculus

  26. Functions are single input. λ-Calculus Break down into smaller pieces

    — currying!
  27. That’s nice. What about functional programming?

  28. λ-Calculus shows the power of functional computation. Functional programming brings

    it to life in computer science!
  29. What is Functional Programming?

  30. – 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?
  31. TL;DR Programming without assignment statements.

  32. Key Concepts • Declarative (vs. Imperative) • First Class Functions

    • Referential Transparency and Purity • Recursion • Immutability • Partial Application and Currying • Composition
  33. Thar be ES6 ahead!

  34. 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']
  35. 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']
  36. 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']
  37. 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; };
  38. 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); }
  39. 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)); }
  40. 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);
  41. Declarative vs. Imperative

  42. 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
  43. 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]
  44. 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]
  45. 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]
  46. 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]
  47. 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]
  48. 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
  49. Declarative function doubleNumber(n) { return n * 2; } function

    doubleNumbers(numbers) { return numbers.map(doubleNumber); } doubleNumbers([1, 2, 3]); // [2, 4, 6]
  50. First Class Functions Functions are expressions/values.

  51. 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); // ''
  52. 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); // ''
  53. 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); // ''
  54. 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); // ''
  55. First Class Functions Functions as arguments

  56. 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); }
  57. 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); }
  58. First Class Functions Functions as return values

  59. 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
  60. Closures

  61. Closures • Allow functions to reference other scopes • “Close

    over” variables in higher scopes • Critical to functional programming paradigms like partial application
  62. Closures // New function “closes” over x function additionFactory(x) {

    return (y) => x + y; } const add1 = additionFactory(1); add1(2); // 3
  63. // New function “closes” over x function additionFactory(x) { return

    (y) => x + y; } const add1 = additionFactory(1); add1(2); // 3 Closures Same x
  64. 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'
  65. 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'
  66. 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
  67. 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'
  68. Closures let myValue = 'foo'; // Becomes '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'
  69. 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'
  70. 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'
  71. Referential Transparency

  72. 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
  73. 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;
  74. 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;
  75. 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;
  76. Referential Transparency let counter = 0; function addToCounter(n) { counter++;

    return n + counter; } // Not referentially transparent let value1 = addToCounter(1) + addToCounter(1); // 5 let value2 = 2 + 3; // Not the same value every call!
  77. Idempotency

  78. Idempotency • Special case of referential transparency • Multiple function

    applications produce the same result as one application
  79. 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
  80. 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
  81. 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
  82. Purity

  83. Purity • “Strict referential transparency” • No side effects •

    No global/shared state • No impure arguments • No I/O
  84. 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(' '); }
  85. 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); }
  86. 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
  87. 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
  88. 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
  89. 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
  90. Recursion

  91. 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
  92. Recursion: Factorial

  93. Recursion: Factorial function factorial(n) { let result = 1; while

    (n > 1) { result *= n--; } return result; } Imperative
  94. 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
  95. Recursion: Factorial function factorial(n) { if (n < 2) {

    return 1; } return n * factorial(n - 1); }
  96. Recursion: Factorial function factorial(n) { if (n < 2) {

    return 1; } return n * factorial(n - 1); } Base case
  97. Recursion: Factorial function factorial(n) { if (n < 2) {

    return 1; } return n * factorial(n - 1); } Recursive call
  98. Recursion: Factorial

  99. Recursion: Factorial factorial(4);

  100. Recursion: Factorial factorial(4); 4 * factorial(3);

  101. Recursion: Factorial factorial(4); 4 * factorial(3); 4 * 3 *

    factorial(2);
  102. Recursion: Factorial factorial(4); 4 * factorial(3); 4 * 3 *

    factorial(2); 4 * 3 * 2 * factorial(1);
  103. Recursion: Factorial factorial(4); 4 * factorial(3); 4 * 3 *

    factorial(2); 4 * 3 * 2 * factorial(1); 4 * 3 * 2 * 1;
  104. Recursion: Factorial factorial(4); 4 * factorial(3); 4 * 3 *

    factorial(2); 4 * 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2;
  105. Recursion: Factorial factorial(4); 4 * factorial(3); 4 * 3 *

    factorial(2); 4 * 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6;
  106. Recursion: Factorial factorial(4); 4 * factorial(3); 4 * 3 *

    factorial(2); 4 * 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24;
  107. What about recursion performance?

  108. Recursion Performance let value = factorial(100000); console.log(value); // ???

  109. Recursion Performance let value = factorial(100000); console.log(value); // ??? RangeError:

    Maximum call stack size exceeded
  110. Recursion: Factorial function factorial(n) { if (n < 2) {

    return 1; } return n * factorial(n - 1); }
  111. Recursion: Factorial function factorial(n) { if (n < 2) {

    return 1; } return n * factorial(n - 1); }
  112. Recursion: Factorial factorial(4); 4 * factorial(3); 4 * 3 *

    factorial(2); 4 * 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24;
  113. Recursion: Factorial 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> Stack
  114. Recursion: Factorial 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) Stack
  115. Recursion: Factorial <main> factorial(4) factorial(3) factorial(4); 4 * factorial(3); 4

    * 3 * factorial(2); 4 * 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; Stack
  116. Recursion: Factorial <main> factorial(4) factorial(3) factorial(4); 4 * factorial(3); 4

    * 3 * factorial(2); 4 * 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; factorial(2) Stack
  117. Recursion: Factorial <main> factorial(4) factorial(3) factorial(4); 4 * factorial(3); 4

    * 3 * factorial(2); 4 * 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; factorial(2) factorial(1) Stack
  118. Recursion: Factorial <main> factorial(4) factorial(3) factorial(2) factorial(4); 4 * factorial(3);

    4 * 3 * factorial(2); 4 * 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; Stack
  119. Recursion: Factorial <main> factorial(4) factorial(3) factorial(4); 4 * factorial(3); 4

    * 3 * factorial(2); 4 * 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; Stack
  120. Recursion: Factorial <main> factorial(4) factorial(4); 4 * factorial(3); 4 *

    3 * factorial(2); 4 * 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; Stack
  121. Recursion: Factorial <main> factorial(4); 4 * factorial(3); 4 * 3

    * factorial(2); 4 * 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2; 4 * 6; 24; Stack
  122. Recursion Performance let value = factorial(100000); console.log(value); // ??? 100,000

    calls = 100,000 stack frames 1 stack frame ≈ 48B Max stack usage ≈ 1MB 100,000 × 48 / 1024 / 1024 = 4.58mb > 1mb
  123. Tail call optimization to the rescue!

  124. Tail Call Optimization

  125. 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!)
  126. Tail Call Optimization Unoptimizable function factorial(n) { if (n <

    2) { return 1; } return n * factorial(n - 1); }
  127. Tail Call Optimization Unoptimizable function factorial(n) { if (n <

    2) { return 1; } return n * factorial(n - 1); } 1
  128. Tail Call Optimization Unoptimizable function factorial(n) { if (n <

    2) { return 1; } return n * factorial(n - 1); } 1 2
  129. Tail Call Optimization Unoptimizable function factorial(n) { if (n <

    2) { return 1; } return n * factorial(n - 1); } 1 3 2
  130. Optimize Factorial function factorial(n, accum = 1) { if (n

    < 2) { return accum; } return factorial(n - 1, n * accum); }
  131. Optimize Factorial function factorial(n, accum = 1) { if (n

    < 2) { return accum; } return factorial(n - 1, n * accum); } Current value accumulator
  132. Optimize Factorial function factorial(n, accum = 1) { if (n

    < 2) { return accum; } return factorial(n - 1, n * accum); } Return the accumulator for base case
  133. Optimize Factorial function factorial(n, accum = 1) { if (n

    < 2) { return accum; } return factorial(n - 1, n * accum); } Move calculation inside the call
  134. Optimize Factorial function factorial(n, accum = 1) { if (n

    < 2) { return accum; } return factorial(n - 1, n * accum); } 1
  135. Optimize Factorial function factorial(n, accum = 1) { if (n

    < 2) { return accum; } return factorial(n - 1, n * accum); } 1 2
  136. Optimize Factorial function factorial(n, accum = 1) { if (n

    < 2) { return accum; } return factorial(n - 1, n * accum); } 1 3 2
  137. And now? let value = factorial(100000); console.log(value); // Infinity

  138. Factorial TCO factorial(4 /*, 1 */); factorial(3, 4); factorial(2, 12);

    factorial(1, 24); 24;
  139. Factorial TCO factorial(4 /*, 1 */); factorial(3, 4); factorial(2, 12);

    factorial(1, 24); 24; <main> Stack
  140. Factorial TCO <main> factorial(4, 1) Stack factorial(4 /*, 1 */);

    factorial(3, 4); factorial(2, 12); factorial(1, 24); 24;
  141. Factorial TCO <main> factorial(3, 4) Stack factorial(4 /*, 1 */);

    factorial(3, 4); factorial(2, 12); factorial(1, 24); 24;
  142. Factorial TCO <main> factorial(2, 12) Stack factorial(4 /*, 1 */);

    factorial(3, 4); factorial(2, 12); factorial(1, 24); 24;
  143. Factorial TCO <main> factorial(1, 24) Stack factorial(4 /*, 1 */);

    factorial(3, 4); factorial(2, 12); factorial(1, 24); 24;
  144. Factorial TCO <main> Stack factorial(4 /*, 1 */); factorial(3, 4);

    factorial(2, 12); factorial(1, 24); 24;
  145. Recursion: Fibonacci

  146. Recursion: Fibonacci

  147. Recursion: Fibonacci Base cases

  148. Recursion: Fibonacci Recurrence

  149. Recursion: Fibonacci function fibonacci(n) { if (n < 2) {

    return n; } return fibonacci(n - 1) + fibonacci(n - 2); }
  150. Recursion: Fibonacci function fibonacci(n) { if (n < 2) {

    return n; } return fibonacci(n - 1) + fibonacci(n - 2); } Base Cases
  151. Recursion: Fibonacci function fibonacci(n) { if (n < 2) {

    return n; } return fibonacci(n - 1) + fibonacci(n - 2); } Recurrence
  152. Recursion: Fibonacci fib(0); // 0 fib(1); // 1 fib(2); //

    1 fib(3); // 2 fib(4); // 3 fib(5); // 5 function fibonacci(n) { if (n < 2) { return n; } return fibonacci(n - 1) + fibonacci(n - 2); }
  153. Performance Revisited fibonacci(1); // 0 ms

  154. Performance Revisited fibonacci(1); // 0 ms fibonacci(5); // 0 ms

  155. Performance Revisited fibonacci(1); // 0 ms fibonacci(5); // 0 ms

    fibonacci(20); // 0 ms
  156. Performance Revisited fibonacci(1); // 0 ms fibonacci(5); // 0 ms

    fibonacci(20); // 0 ms fibonacci(30); // 13 ms
  157. Performance Revisited fibonacci(1); // 0 ms fibonacci(5); // 0 ms

    fibonacci(20); // 0 ms fibonacci(30); // 13 ms fibonacci(35); // 131 ms
  158. 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
  159. 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!
  160. 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
  161. Exponential Growth Runtime (ms) 0 4500 9000 13500 18000 Nth

    number of Fibonacci Sequence 0 10 20 30 40 50
  162. Recursive Call Tree

  163. Recursive Call Tree Computing some of the same values several

    times!
  164. Recursive Call Tree Computing some of the same values several

    times! fibonacci(4) x 2
  165. Recursive Call Tree Computing some of the same values several

    times! fibonacci(4) x 2 fibonacci(3) x 3
  166. Recursive Call Tree Computing some of the same values several

    times! fibonacci(4) x 2 fibonacci(3) x 3 fibonacci(2) x 5
  167. 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
  168. TCO and Dynamic Programming

  169. 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
  170. Fibonacci TCO function fibonacci(n, current = 0, next = 1)

    { if (n === 0) { return current; } return fibonacci(n - 1, next, current + next); }
  171. Fibonacci TCO function fibonacci(n, current = 0, next = 1)

    { if (n === 0) { return current; } return fibonacci(n - 1, next, current + next); } Two accumulators
  172. 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
  173. 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
  174. Fibonacci TCO fibonacci(6 /*, 0, 1 */); fibonacci(5, 1, 1);

    fibonacci(4, 1, 2); fibonacci(3, 2, 3); fibonacci(2, 3, 5); fibonacci(1, 5, 8); fibonacci(0, 8, 13); 8;
  175. Fibonacci TCO fibonacci(6 /*, 0, 1 */); fibonacci(5, 1, 1);

    fibonacci(4, 1, 2); fibonacci(3, 2, 3); fibonacci(2, 3, 5); fibonacci(1, 5, 8); fibonacci(0, 8, 13); 8; Fibonacci sequence
  176. 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
  177. Partial Application

  178. 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
  179. 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
  180. 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
  181. 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
  182. 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
  183. Partial Application function partial(fn, ...args) { return (...otherArgs) => {

    return fn(...args, ...otherArgs); }; }
  184. Partial Application function partial(fn, ...args) { return (...otherArgs) => {

    return fn(...args, ...otherArgs); }; } Applied arguments
  185. Partial Application function partial(fn, ...args) { return (...otherArgs) => {

    return fn(...args, ...otherArgs); }; } Return new closure
  186. Partial Application function partial(fn, ...args) { return (...otherArgs) => {

    return fn(...args, ...otherArgs); }; } Remaining arguments
  187. Partial Application function partial(fn, ...args) { return (...otherArgs) => {

    return fn(...args, ...otherArgs); }; } Call original function with all arguments
  188. Partial Application: Why? • Build up functions from smaller pieces

    • Remove duplication • Generalize function abstraction (i.e. no additionFactory type functions)
  189. Partial Application: Why? function multiply(x, y) { return x *

    y; } const doubleNumber = partial(multiply, 2); const numbers = [1, 2, 3]; const doubledNumbers = map(doubleNumber, numbers); console.log(doubledNumbers); // [2, 4, 6]
  190. Currying

  191. 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?)
  192. 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
  193. 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
  194. 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
  195. 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
  196. 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
  197. 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
  198. Currying function curry(fn, len = fn.length) { return (...args) =>

    { if (args.length >= len) { return fn(...args); } return curry( partial(fn, ...args), len - args.length ); }; }
  199. 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!
  200. 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)
  201. 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
  202. 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
  203. 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
  204. 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
  205. 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]
  206. Function Composition

  207. Function Composition

  208. Function Composition • Compose functions together to form new functions

    • Pipe function output to next function • Helps enforce modularity/ SOC by keeping functions small
  209. 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
  210. 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
  211. 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
  212. 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)); }
  213. 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)); }
  214. 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)); }
  215. 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)); }
  216. 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)); }
  217. </fp-basics>

  218. But why Functional Programming?

  219. Why FP • Concise, elegant solutions to problems • Modularity

    and composition • Eliminate data races with immutability • Unit tests are simpler — no checking for state changes
  220. • Prof. Frisby’s Mostly Adequate…
 (github.com/MostlyAdequate/mostly-adequate-guide) • Babel (babeljs.io) •

    Lodash (lodash.com) • Ramda (ramdajs.com) • Clojurescript (github.com/clojure/clojurescript) • Immutable.js (github.com/facebook/immutable-js) • React (facebook.github.io/react/) • Redux (redux.js.org)
  221. Thanks! jfairbank @elpapapollo blog.jeremyfairbank.com Code samples (some ES5 too) github.com/jfairbank/functional-javascript