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

Ramda from Zero to Hero

Ramda from Zero to Hero

Functional programming is the buzz nowadays. Learn what’s it all about and get a taste of how Ramda library helps you write functional code.

Jakub Barczyk

July 27, 2017
Tweet

More Decks by Jakub Barczyk

Other Decks in Programming

Transcript

  1. WHO AM I? Jakub Barczyk Front-End Engineer at Ryanair LinkedIn:

    /in/jakubbarczyk Twitter: @jakubbarczyk
  2. FIRST-CLASS CITIZENS In JavaScript, functions are objects : Object.getPrototypeOf(Function.prototype); //

    => Object Object.prototype === Object.getPrototypeOf(Function.prototype); // => true
  3. FIRST-CLASS CITIZENS In JavaScript, functions are objects : Object.getPrototypeOf(Function.prototype); //

    => Object Object.prototype === Object.getPrototypeOf(Function.prototype); // => true One may as well set properties on them: function foo() {...} foo.sampleProp = 'bar'; foo.sampleProp; // => 'bar'
  4. HIGHER-ORDER FUNCTIONS "Functions that operate on other functions, either by

    taking them as arguments or by returning them" — Eloquent JavaScript function foo() {...} function bar(arg) { return arg(); } bar(foo);
  5. PURE FUNCTIONS return the same result given the same arguments

    do not modify external state // impure function function namePerson(person) { return Object.defineProperty(person, 'name', {value: 'Joe'}); } // pure function function namePerson(person) { return Object.assign({}, person, {name: 'Joe'}); }
  6. FUNCTIONAL PROGRAMMING (FP) The process of building software by composing

    pure functions. Functional code tends to be: concise predictable easier to test
  7. FUNCTION COMPOSITION The application of one function to the result

    of another function. f( g(x) ) (f ° g)(x)
  8. FUNCTION COMPOSITION The application of one function to the result

    of another function. f( g(x) ) (f ° g)(x) The simplest example of composition in JavaScript. function doStuff(arg) {...} function doMoreStuff(arg) {...} [...].map((x) => doMoreStuff(doStuff(x))); [...].map(doStuff).map(doMoreStuff);
  9. WHY RAMDA? At rst, it may seem Ramda is a

    duplicate of libraries such as Underscore or lodash. // Ramda // Underscore // lodash R.flatten(...) _.flatten(...) _.flatten(...) R.find(...) _.find(...) _.find(...) R.map(...) _.map(...) _.map(...) R.uniq(...) _.uniq(...) _.uniq(...) R.without(...) _.without(...) _.without(...)
  10. FUNCTIONAL IN PURPOSE Ramda provides a number of iteratee- rst,

    data-last methods as opposed to, for example, lodash. // lodash _.difference([ ... ], (x) => {...}); _.map([ ... ], (x) => {...}); _.takeWhile([ ... ], (x) => {...}); // Ramda R.difference((x) => {...}, [ ... ]); R.map((x) => {...}, [ ... ]); R.takeWhile((x) => {...}, [ ... ]);
  11. CURRYING The pattern of reducing functions of more than one

    argument to functions of one argument. — Haskell B. Curry // ES5 function add(a) { return function (b) { return a + b; }; } // ES6 const add = (a) => (b) => a + b; const sum = add(2)(4); // => 6
  12. PARTIAL APPLICATION The pattern of pre-applying arguments to a function.

    // ES5 function printSuperhero(a, b) { return function (c) { return a + ' ' + b + ' is ' + c; }; } // ES6 const printSuperhero = (a, b) => (c) => `${a} ${b} is ${c}`; printSuperhero('Clark', 'Kent')('Superman'); // => 'Clark Kent is Superman'
  13. CURRIED FUNCTIONS Ramda functions are curried. const add2 = add(2);

    // => Function const total = add2(10); // => 12
  14. CURRIED FUNCTIONS Ramda functions are curried. const add2 = add(2);

    // => Function const total = add2(10); // => 12 const isGreaterThan20 = gt(__, 20); // => Function isGreaterThan20(19); // => false isGreaterThan20(42); // => true
  15. CURRIED FUNCTIONS Ramda functions are curried. const add2 = add(2);

    // => Function const total = add5(10); // => 12 const isGreaterThan20 = gt(__, 20); // => Function isGreaterThan20(19); // => false isGreaterThan20(42); // => true const isChildFromPoland = where({ age: lte(__, 3), nationality: equals('Polish') }); // => Function isChildFromPoland({ age: 3, nationality: 'German' }); // => false isChildFromPoland({ age: 6, nationality: 'German' }); // => false isChildFromPoland({ age: 2, nationality: 'Polish' }); // => true
  16. CURRYING WITH RAMDA Find book titles for a given year.

    const publishedInYear = curry((year, book) => year === book.year); const titlesForYear = (year, books) => { const selected = filter(publishedInYear(year), books); return map((book) => book.title, selected); };
  17. CURRYING WITH RAMDA Find book titles for a given year.

    const publishedInYear = curry((year, book) => year === book.year); const titlesForYear = (year, books) => { const selected = filter(publishedInYear(year), books); return map((book) => book.title, selected); }; How to improve? const titlesForYear = (year) => pipe( filter(publishedInYear(year)), map(prop('title')) );
  18. THINKING FUNCTIONALLY #1 Double each number in the array. const

    double = (num) => 2 * num; map(double, [1, 3, 5]); // => [2, 6, 10]
  19. THINKING FUNCTIONALLY #1 Double each number in the array. const

    double = (num) => 2 * num; map(double, [1, 3, 5]); // => [2, 6, 10] Add up all numbers in the array. const add = (acc, val) => acc + val; reduce(add, 10, [1, 2, 3, 4]); // => 20
  20. THINKING FUNCTIONALLY #1 Double each number in the array. const

    double = (num) => 2 * num; map(double, [1, 3, 5]); // => [2, 6, 10] Add up all numbers in the array. const add = (acc, val) => acc + val; reduce(add, 10, [1, 2, 3, 4]); // => 20 Filter even and odd numbers. const isEven = (num) => num % 2 === 0; filter(isEven, [1, 2, 3, 4]); // => [2, 4] reject(isEven, [1, 2, 3, 4]); // => [1, 3]
  21. THINKING FUNCTIONALLY #2 What if an isOdd function is needed?

    const isEven = (num) => num % 2 === 0; find(complement(isEven), [3, 5, 8, 13]); // => 3
  22. THINKING FUNCTIONALLY #2 What if an isOdd function is needed?

    const isEven = (num) => num % 2 === 0; find(complement(isEven), [3, 5, 8, 13]); // => 3 It can be coded with almost no effort. const isEven = (num) => num % 2 === 0; const isOdd = complement(isEven); find(isOdd, [3, 5, 8, 13]); // => 3
  23. THINKING FUNCTIONALLY #3 How about handling conditions? const isPolish =

    (person) => person.nationality === 'Polish'; const isAdult = (person) => person.age >= 18; const isEligibleToVoteInPoland = both(isPolish, isAdult); const aPerson = { name: 'Janusz', age: 41, nationality: 'Polish' }; isEligibleToVoteInPoland(aPerson); // => true
  24. THINKING FUNCTIONALLY #4 Handling even more complex conditions. const temperature

    = cond([ [equals(0), always('water freezes at 0 degrees Celsius')], [equals(100), always('water boils at 100 degrees Celsius')], [T, (temp) => `nothing special happens at ${temp} degrees Celsius`] ]); temperature(0); // => 'water freezes at 0 degrees Celsius' temperature(50); // => 'nothing special happens at 50 degrees Celsius' temperature(100); // => 'water boils at 100 degrees Celsius'
  25. THINKING FUNCTIONALLY #5 Running mathematical operations. const addOne = add(1);

    const double = multiply(2); const doMathWithPipe = pipe( addOne, // => [2, 3, 4, 5] double, // => [4, 6, 8, 10] sum // => 28 ); doMathWithPipe([1, 2, 3, 4]); // => 28
  26. THINKING FUNCTIONALLY #5 Running mathematical operations. const addOne = add(1);

    const double = multiply(2); const doMathWithPipe = pipe( addOne, // => [2, 3, 4, 5] double, // => [4, 6, 8, 10] sum // => 28 ); doMathWithPipe([1, 2, 3, 4]); // => 28 const doMathWithCompose = compose(sum, double, addOne); doMathWithCompose([1, 2, 3, 4]); // => 28
  27. THINKING FUNCTIONALLY #6 Pull out a property from an object.

    prop('foo', {foo: 100, bar: 200}); // => 100 prop('moo', {foo: 100, bar: 200}); // => undefined
  28. THINKING FUNCTIONALLY #6 Pull out a property from an object.

    prop('foo', {foo: 100, bar: 200}); // => 100 prop('moo', {foo: 100, bar: 200}); // => undefined How about pulling out a couple of properties? props(['foo', 'bar'], {foo: 1, bar: 2, baz: 3}); // => [1, 2] props(['foo', 'baz'], {foo: {bar: 1}, baz: null}); // => [{"bar": 1}, null]
  29. THINKING FUNCTIONALLY #7 Pull out some properties from an array

    of objects. const objects = [ {a: 1, b: 2, c: 3}, {a: 4, b: 5, c: 6}, {a: 7, b: 8, c: 9} ]; const pickProps = map(pick(['a', 'c'])); // => Function pickProps(objects); // => [{a: 1, c: 3}, {a: 4, c: 6}, {a: 7, c: 9}]
  30. THINKING FUNCTIONALLY #7 Pull out some properties from an array

    of objects. const objects = [ {a: 1, b: 2, c: 3}, {a: 4, b: 5, c: 6}, {a: 7, b: 8, c: 9} ]; const pickProps = map(pick(['a', 'c'])); // => Function pickProps(objects); // => [{a: 1, c: 3}, {a: 4, c: 6}, {a: 7, c: 9}] There's a better way of doing that. const pickProps = project(['a', 'c']); // => Function pickProps(objects); // => [{a: 1, c: 3}, {a: 4, c: 6}, {a: 7, c: 9}]
  31. MAKING OBJECTS AWESOME Transform multiple object properties on the y.

    const ordinaryDude = { name: ' Prince Adam ', power: '12', specials: [ {type: 'Super Kick', accuracy: 3}, {type: 'Super Punch', accuracy: 5} ] }; const makeAwesome = evolve({ name: always('He-Man'), power: multiply(10), specials: map(merge(__, {accuracy: 10})) }); const extraordinaryDude = makeAwesome(ordinaryDude); Try it in REPL
  32. RAMDA USE CASE #1 Consider calculating fares. const groupByCode =

    groupWith(eqProps('code')); const sumFares = reduce( // Reducing function (acc, tally) => evolve({ qty: add(acc.qty), amt: add(acc.amt), total: add(acc.total) }, tally), // Starting object { qty: 0, amt: 0, total: 0 } ); const calculateFares = compose(map(sumFares), groupByCode); Try it in REPL
  33. RAMDA USE CASE #2 Consider encoding children. const encodeChildren =

    (rooms) => { const childAgeString = (child) => `${child.age}`; const encode = (room) => { if (room.children.length) { const children = map(childAgeString, room.children); return `${room.adults}|${join(':', children)}`; } else { return `${room.adults}`; } }; return map(encode, rooms).join(); }; encodeChildren(rooms); // => adult,adult|child:child,adult|child... Try it in REPL
  34. RAMDA USE CASE #2 Ramdify the implementation. const childAgeString =

    compose(toString, prop('age')); const children = compose(join(':'), map(childAgeString), prop('children')); const formatAdultsChildren = (room) => `${room.adults}|${children(room)}`; const formatAdults = compose(toString, prop('adults')); const hasChildren = compose(gt(__, 0), length, prop('children')); const encode = ifElse(hasChildren, formatAdultsChildren, formatAdults); const encodeChildren = compose(join(','), map(encode)); encodeChildren(rooms); // => adult,adult|child:child,adult|child... Try it in REPL
  35. AVOID OVERKILLS There's no gain in using Ramda everywhere. let

    foo = true; let bar = false; // DON'T if (equals(foo, bar)) { ... } // DO if (foo === bar) { ... }
  36. AVOID OVERKILLS There's no gain in using Ramda everywhere. let

    foo = true; let i = 1; let bar = false; let j = 10; // DON'T // DON'T if (equals(foo, bar)) { while (lt(i, j)) { ... i = inc(i); } } // DO // DO if (foo === bar) { while (i < j) { ... ++i; } }