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

Intro to Functional Style Programming in JS

Intro to Functional Style Programming in JS

An introductory look at programming in a functional style in JavaScript. We’ll use the Ramda utility library (akin to Underscore or Lodash) to learn about function composition, currying, higher order functions, immutability and more to build applications in a pure and principled way. Along the way, we’ll shape some of the intuitions necessary for thinking about how data flows through the programs we write while recognizing and taming side effects. Examples will be applicable to both the server and client, with a particular spotlight on a Redux-flavored React app.

Code and other resources can be found here at https://github.com/jimf/intro-fp-js-talk.

This talk was given to the Bucks County JS meetup on May 4, 2016.

Jim Fitzpatrick

May 04, 2016
Tweet

More Decks by Jim Fitzpatrick

Other Decks in Programming

Transcript

  1. 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.
  2. 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. *
  3. Immutability • Reduce or eliminate (re)assignment • Variables are inherently

    more complex than values • Object.freeze / Object.seal • Immutable data structures for added perf (e.g., Immutable.js)
  4. let foo = 1; let bar = foo; bar +=

    1; console.log(foo);
  5. let foo = 1; let bar = foo; bar +=

    1; console.log(foo); //=> 1
  6. let foo = 'foo'; let bar = foo; bar +=

    'bar'; console.log(foo);
  7. let foo = 'foo'; let bar = foo; bar +=

    'bar'; console.log(foo); //=> "foo"
  8. const foo = [1, 2, 3]; const bar = foo;

    bar.push(4); console.log(foo);
  9. const foo = [1, 2, 3]; const bar = foo;

    bar.push(4); console.log(foo); //=> [1, 2, 3, 4]
  10. const foo = { foo: 1, bar: 2, baz: 3

    }; const bar = foo; bar.foo = 3; console.log(foo);
  11. const foo = { foo: 1, bar: 2, baz: 3

    }; const bar = foo; bar.foo = 3; console.log(foo); //=> { foo: 3, bar: 2, baz: 3 }
  12. import { append } from 'ramda'; const foo = [1,

    2, 3]; const bar = append(4, foo); console.log(foo); //=> [1, 2, 3] console.log(bar); //=> [1, 2, 3, 4]
  13. Purity • Output only relies on inputs • Deterministic •

    No “side-effects” • AKA “Referential transparency”
  14. Possible indicators of impurity: Function... • takes no arguments •

    returns void • makes reference to this • uses globals
  15. // Impure: returns void and modifies the environment: console.log('Hello world!');

    // Impure: receives no arguments and return value is // non-determinisitic: Math.random(); // Impure: Modifies the array as a side-effect myArray.splice(2, 3);
  16. Higher Order Functions Do one or both of: • Receive

    one or more functions as arguments • Return a function as a result
  17. Higher Order Functions The pillars: • map ([a]→[b]) - result

    has same shape, possibly different value type • filter • reduce
  18. Higher Order Functions The pillars: • map ([a]→[b]) - result

    has same shape, possibly different value type • filter ([a]→[a]) - result has same shape, possibly smaller • reduce
  19. Higher Order Functions The pillars: • map ([a]→[b]) - result

    has same shape, possibly different value type • filter ([a]→[a]) - result has same shape, possibly smaller • reduce ([a]→b) - result may be completely transformed
  20. Higher Order Functions The pillars: • map ([a]→[b]) - result

    has same shape, possibly different value type • filter ([a]→[a]) - result has same shape, possibly smaller • reduce ([a]→b) - result may be completely transformed Important: map and filter can be implemented with reduce!
  21. [1, 2, 3].map(n => n + 1); //=> [2, 3,

    4] [1, 2, 3].filter(n => n % 2 === 0); //=> [2] [1, 2, 3].reduce((acc, n) => acc + n, 0); // 0 + 1 = 1 // 1 + 2 = 3 // 3 + 3 = 6 //=> 6
  22. // Naive unary function. Decorates a given // function to

    accept a single argument. const unary = fn => { return (...args) => fn(args[0]); }; // Same as above, but more succinct. const unary = fn => ([first]) => fn(first); ['1', '2', '3'].map(parseInt); //=> [1, NaN, NaN] ['1', '2', '3'].map(unary(parseInt)); //=> [1, 2, 3]
  23. Imperative PB&J • Gather sliced sandwich bread, peanut butter, jelly,

    butter knife and plate • Place 2 slices of the bread flat on the plate, side-by-side • Evenly spread peanut butter over the top side of the left-most slice of bread • Evenly spread jelly over the top side of the right-most slice of bread • Place the peanut butter bread on top of the jelly bread, with the peanut butter side facing down and the jelly side facing up, and the corners of each slice of bread meeting • Remove crust if desired • Enjoy!
  24. const numbers = [1, 2, 3, 4, 5]; // Imperative

    doubling let doubled = []; for (let i = 0; i < numbers.length; i++) { doubled.push(numbers[i] * 2); } // Declarative doubling const doubled2 = numbers.map(n => n * 2); console.log(doubled, doubled2); //=> [2, 4, 6, 8, 10] //=> [2, 4, 6, 8, 10]
  25. const numbers = [1, 2, 3, 4, 5]; // Imperative

    summation let total = 0; for (let i = 0; i < numbers.length; i++) { total += numbers[i]; } // Declarative summation const total2 = numbers.reduce(((acc, n) => acc + n), 0); console.log(total, total2); //=> 15 //=> 15
  26. Currying • Close cousin of partial application (Function.prototype.bind) • Return

    new, partially applied functions until all args are received • Data-last • Used to create specialized functions from more generic ones • Enables composition
  27. // Traditional add function let add = (a, b) =>

    a + b; add(3, 5); //=> 8 add(3)(5); //=> TypeError
  28. // Traditional add function let add = (a, b) =>

    a + b; add(3, 5); //=> 8 add(3)(5); //=> TypeError // Pseudo-curried add using ES6 arrow syntax let add = a => b => a + b; add(3, 5); //=> [Function] add(3)(5); //=> 8
  29. // Traditional add function let add = (a, b) =>

    a + b; add(3, 5); //=> 8 add(3)(5); //=> TypeError // Pseudo-curried add using ES6 arrow syntax let add = a => b => a + b; add(3, 5); //=> [Function] add(3)(5); //=> 8 // Curried with Ramda import { curry, map } from 'ramda'; let add = curry((a, b) => a + b); add(3, 5); //=> 8 add(3)(5); //=> 8
  30. // Traditional add function let add = (a, b) =>

    a + b; add(3, 5); //=> 8 add(3)(5); //=> TypeError // Pseudo-curried add using ES6 arrow syntax let add = a => b => a + b; add(3, 5); //=> [Function] add(3)(5); //=> 8 // Curried with Ramda import { curry, map } from 'ramda'; let add = curry((a, b) => a + b); add(3, 5); //=> 8 add(3)(5); //=> 8 map(add(3), [1, 2, 3]); //=> [4, 5, 6]
  31. // Special Ramda currying placeholder: R.__ import R from 'ramda';

    const reciprocal = R.divide(1); const half = R.divide(R.__, 2); const isOdd = R.modulo(R.__, 2); reciprocal(4); //=> 0.25 half(8); //=> 4 isOdd(3); //=> 1 isOdd(4); //=> 0 R.filter(isOdd, [1, 2, 3, 4, 5]); //=> [1, 3, 5]
  32. // Alternative to R.__: R.flip import R from 'ramda'; const

    reciprocal = R.divide(1); const half = R.flip(R.divide)(2); const isOdd = R.flip(R.modulo)(2); reciprocal(4); //=> 0.25 half(8); //=> 4 isOdd(3); //=> 1 isOdd(4); //=> 0 R.filter(isOdd, [1, 2, 3, 4, 5]); //=> [1, 3, 5]
  33. Function Composition • “Glue” functions together to build new functions

    • Take output from one function and pipe into another as input • Traditionally applies functions from right to left • Enables generic, “pointfree” form (inputs are unnamed) • compose(f, g, h)(x) → f(g(h(x))) • Eschews “uncomposable” mechanisms (e.g., throw, new)
  34. import { compose, prop, toUpper } from 'ramda'; // Long

    form const upperName = obj => { name = prop('name', obj); return toUpper(name); }; upperName({ name: 'Sam' }); //=> "SAM" // Pointfree (inputs are unnamed and generic) const upperName = compose(toUpper, prop('name')); upperName({ name: 'Carol' }); //=> "CAROL"
  35. import { compose, map, prop } from 'ramda'; const getCartItems

    = compose(prop('records'), ShoppingCart.findAll); const formatItemName = compose(titlize, prop('name')); const listItem = compose(makeTag('li'), xmlescape); const cartToList = compose(makeTag('ul'), map(compose(listItem, formatItemName)), getCartItems);
  36. Tripping Points • Documenting curried/pointfree functions • Forgetting to supply

    final arg to curried functions • Debugging • Code structure / organization
  37. Objective • Build deterministic psudo-random number generator (link) • Decide

    on an entropy source • Grab RSVPs via Meetup API • Use RNG to choose the winner