Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CodeStock: Functional Programming Basics in JavaScript

CodeStock: Functional Programming Basics in JavaScript

Jeremy Fairbank

July 11, 2015
Tweet

More Decks by Jeremy Fairbank

Other Decks in Programming

Transcript

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

    the domain with exactly one element in the range. Domain Range
  2. Alonzo Church • Church-Turing Thesis • Lambda Calculus • Represent

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

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

    variable binding and substitution.” λ-Calculus Substitution => apply arguments
  5. – 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?
  6. Key Concepts • Declarative (vs. Imperative) • First Class Functions

    • Referential Transparency and Purity • Recursion • Immutability • Partial Application and Currying • Composition
  7. 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']
  8. 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']
  9. 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']
  10. 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; };
  11. 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); }
  12. 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)); }
  13. 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);
  14. 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
  15. 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]
  16. 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]
  17. 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]
  18. 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]
  19. 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]
  20. 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
  21. Declarative function doubleNumber(n) { return n * 2; } function

    doubleNumbers(numbers) { return numbers.map(doubleNumber); } doubleNumbers([1, 2, 3]); // [2, 4, 6]
  22. 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); // ''
  23. 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); // ''
  24. 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); // ''
  25. 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); // ''
  26. 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); }
  27. 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); }
  28. 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
  29. 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
  30. 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
  31. Closures • Allow functions to reference other scopes • “Close

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

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

    (y) => x + y; } const add1 = additionFactory(1); add1(2); // 3 Closures Same x
  34. 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'
  35. 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'
  36. 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
  37. 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'
  38. 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'
  39. 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'
  40. 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'
  41. 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
  42. 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;
  43. 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;
  44. 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;
  45. 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;
  46. 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;
  47. 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;
  48. Idempotency • Special case of referential transparency • Multiple function

    applications produce the same result as one application
  49. 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
  50. 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
  51. 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
  52. Purity • “Strict referential transparency” • No side effects •

    No global/shared state • No impure arguments • No I/O
  53. 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(' '); }
  54. 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(' '); }
  55. 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(' '); }
  56. 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(' '); }
  57. 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.
  58. 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); }
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. Recursion: Factorial function factorial(n) { let result = 1; while

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

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

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

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

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

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

    factorial(2); 4 * 3 * 2 * factorial(1); 4 * 3 * 2 * 1; 4 * 3 * 2;
  72. 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;
  73. 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;
  74. Recursion: Factorial function factorial(n) { if (n < 2) {

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

    return 1; } return n * factorial(n - 1); }
  76. 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;
  77. 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
  78. 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
  79. 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
  80. 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
  81. 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
  82. 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
  83. 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
  84. 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
  85. 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
  86. 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
  87. 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!)
  88. Tail Call Optimization Unoptimizable function factorial(n) { if (n <

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

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

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

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

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

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

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

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

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

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

    < 2) { return accum; } return factorial(n - 1, n * accum); } 1 3 2
  99. Factorial TCO <main> factorial(4, 1) Stack factorial(4 /*, 1 */);

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

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

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

    factorial(3, 4); factorial(2, 12); factorial(1, 24); 24;
  103. Recursion: Fibonacci function fibonacci(n) { if (n < 2) {

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

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

    return n; } return fibonacci(n - 1) + fibonacci(n - 2); } Recurrence
  106. 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); }
  107. Performance Revisited fibonacci(1); // 0 ms fibonacci(5); // 0 ms

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

    fibonacci(20); // 0 ms fibonacci(30); // 13 ms fibonacci(35); // 131 ms
  109. 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
  110. 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!
  111. 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
  112. Exponential Growth Runtime (ms) 0 4500 9000 13500 18000 Nth

    number of Fibonacci Sequence 0 10 20 30 40 50
  113. Recursive Call Tree Computing some of the same values several

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

    times! fibonacci(4) x 2 fibonacci(3) x 3 fibonacci(2) x 5
  115. 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
  116. 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
  117. Fibonacci TCO function fibonacci(n, current = 0, next = 1)

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

    { if (n === 0) { return current; } return fibonacci(n - 1, next, current + next); } Two accumulators
  119. 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
  120. 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
  121. 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;
  122. 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
  123. 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
  124. Functional Arrays • Common operations like map can be implemented

    in FP • Enforce immutability by returning new array • No looping; use recursion!
  125. 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)]; }
  126. 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
  127. 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
  128. 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];
  129. 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);
  130. 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
  131. 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));
  132. 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.
  133. 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];
  134. 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
  135. 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
  136. 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
  137. 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
  138. 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
  139. Partial Application function partial(fn, ...args) { return (...otherArgs) => {

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

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

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

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

    • Remove duplication • Generalize function abstraction (i.e. no additionFactory type functions)
  144. 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]
  145. 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?)
  146. 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
  147. 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
  148. 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
  149. 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
  150. 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
  151. 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
  152. Currying function curry(fn, len = fn.length) { return (...args) =>

    { if (args.length >= len) { return fn(...args); } return curry( partial(fn, ...args), len - args.length ); }; }
  153. 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!
  154. 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)
  155. 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
  156. 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
  157. 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
  158. 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
  159. 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]
  160. Function Composition • Compose functions together to form new functions

    • Pipe function output to next function • Helps enforce modularity/ SOC by keeping functions small
  161. 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
  162. 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
  163. 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
  164. 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)); }
  165. 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)); }
  166. 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)); }
  167. 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)); }
  168. 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)); }
  169. Why FP • Concise, elegant solutions to problems • Modularity

    and composition • Eliminate data races with immutability • Unit tests are simpler — no checking for state changes
  170. Extra Resources • Babel (babeljs.io) • Lodash (lodash.com) • Clojurescript

    (github.com/clojure/clojurescript) • Immutable.js (github.com/facebook/immutable-js) • React (facebook.github.io/react/)