Introduction à Lodash/fp

Introduction à Lodash/fp

Lodash/fp ?

Lodash ?

Functional programming ?

Lodash

 

  • (200+) méthodes utilitaires généralistes et rapides

 

  • There is a Lodash method for that

 

Exemple - _.get

const _ = require('lodash');

var object = {
  a: {
    b: {
      c: 100
    }
  }
};

_.get(object, 'a.b.c');
// => 100

_.get(object, 'x.y.z');
// => undefined

object.x.y.z
// => TypeError: Cannot read property 'y' of undefined

_.get(object, 'x.y.z', DEFAULT_VALUE);
// => DEFAULT_VALUE
const _ = require('lodash');

var object = {
  a: {
    b: {
      c: 100
    }
  }
};

_.set(object, 'a.b.c', 10000);
// => { a: { b: { c: 10000 } } }

_.set(object, 'x.y.z', 5);
// => { a: { b: { c: 10000 } }, x: { y: { z: 5 } } }

// En natif
object.x = object.x || {};
object.x.y = object.x.y || {};
object.x.y.z = 5;

Exemple - _.set

Functional programming ?

  • Fonctions pures

 

  • Données immutables

 

  • Effets contrôlés (vs side-effects)

 

FP

Fonctions pures

  • arguments => valeur de retour
  • Pas de side-effects
    • mutation de données
    • logging
    • requêtes HTTP
function incrementA(object) {
    object.a++;
}

let b = 100;

function getB() {
  return b;
}

function addAndLog(a, b) {
  console.log('Adding', a, b);
  return a + b;
}

function makeHttpRequest(cb) {
    var oReq = new XMLHttpRequest();
    oReq.addEventListener("load", cb);
    oReq.open("GET", "http://www.example.org/example.txt");
    oReq.send();
}
function add(a, b) {
  return a + b;
}

Pure

Impure

Bénéfices

  • Compréhension plus facile du programme
  • Tests plus faciles
  • Développement par composition de fonctions

Donc...

Lodash/fp

  • Variante officielle de Lodash
  • Orienté vers FP, similaire à Ramda
  • Mêmes méthodes, mais différentes
const _ = require('lodash/fp');

Ordre des paramètres

_.get(data, path);

Vanilla

_.get(path, data);

FP

Arguments non-modifiés

var data = {a: 1};
_.set(data, 'a', 5);
// => {a: 5}

data
// {a: 5}

Vanilla

var data = {a: 1};
_.set('a', 5, data);
// => {a: 5}

data
// {a: 1}

FP

Currying

_.set('a.b.c')(10000, data);
_.set('a.b.c')(10000)(data);
_.set('a.b.c', 10000)(data);

_.set('a.b.c', 10000, data);

_.set('a.b.c')(_, data)(10000);

Méthode curried:

    Méthode, qui, lorsqu'elle reçoit moins de paramètres       qu'attendus, retourne une nouvelle fonction qui                 attend le reste des paramètres.

Currying

const add = _.curry(function(a, b) {
  return a + b;
});

const add7 = add(7);

add7(10);
// => 17

Currying

var disciplineModule  = _.find(discipline.modules,  {ref: SOME_REF});
var achievementModule = _.find(achievement.modules, {ref: SOME_REF});

Vanilla

var findByRef = _.find({ref: SOME_REF});
var disciplineModule  = findByRef(discipline.modules);
var achievementModule = findByRef(achievement.modules);

FP

Nombre fixe de paramètres

_.get('a', {});
// => undefined

_.getOr(100, 'a', {}); // getOr n'existe pas dans vanilla Lodash
// => 100

Currying ou paramètres optionnels, il faut choisir

 

+ de méthodes dans lodash/fp pour combler

Arguments des iteratees

_.map(['a', 'b', 'c'], function(value, index) {
  return index + ": " + value;
});
// => [ '0: a', '1: b', '2: c' ]

Vanilla

FP

_.map(function(value, index) {
  return index + ": " + value;
}, ['a', 'b', 'c']);
// => ?

Arguments des iteratees

_.map(['a', 'b', 'c'], function(value, index) {
  return index + ": " + value;
});
// => [ '0: a', '1: b', '2: c' ]

Vanilla

FP

_.map(function(value, index) {
  return index + ": " + value;
}, ['a', 'b', 'c']);
// => [ 'undefined: a', 'undefined: b', 'undefined: c' ]

Arguments des iteratees

_.map(['6', '8', '10'], parseInt);
// → [6, NaN, 2]

// égal à
[
  //       value  index
  parseInt('6',     0),
  parseInt('8',     1),
  parseInt('10',    2)
]

On ne passe plus qu'un seul élément.

Cool, mais en pratique ?

Composition de fonctions

Avec _.flow / _.pipe ou _.flowRight / _.compose

// Avec flow / pipe
const fn = _.pipe(f, g);

fn(x) === g(f(x));

// Avec flowRight / compose
const fn = _.compose(g, f);

fn(x) === g(f(x));

Composition de fonctions

const foods = [
  {name: 'Japonais rue Saint-Anne', amount: 9, healthy: true},
  {name: 'Big Fernand', amount: 12, healthy: false},
  {name: 'Fish and Chips', amount: 12.5, healthy: false},
  {name: 'Sushi Shop', amount: 18, healthy: true},
  {name: 'Kebab', amount: 5.5, healthy: false}
];
const getTodaysMeal = _.pipe(
  _.filter({healthy: false}),
  _.shuffle,
  _.find(_.pipe(
   _.property('amount'),
   _.gt(_, 10)
  )),
  _.property('name'),
  _.toUpper
);

console.log(getTodaysMeal(foods));
// => FISH AND CHIPS
function getTodaysMeal(foods) {
  const unhealthy = _.shuffle(
    _.filter(foods, {healthy: false})
  );
  const meal = _.find(unhealthy, function(item) {
    return item.amount > 10;
  });
  return _.toUpper(meal.name);
}

console.log(getTodaysMeal(foods));
// => FISH AND CHIPS

Composition de fonctions

function getTodaysMeal(foods) {
  const unhealthy = _.shuffle(
    _.filter(foods, {healthy: false})
  );
  const meal = _.find(unhealthy, function(item) {
    return item.amount > 10;
  });
  return _.toUpper(meal.name);
}

console.log(getTodaysMeal(foods));
// => FISH AND CHIPS
  • Déclaration d'actions, pas de variables
  • Pas précisé d'arguments (point-free style)
const getTodaysMeal = _.pipe(
  _.filter({healthy: false}),
  _.shuffle,
  _.find(_.pipe(
   _.property('amount'),
   _.gt(_, 10)
  )),
  _.property('name'),
  _.toUpper
);

console.log(getTodaysMeal(foods));
// => FISH AND CHIPS

"Y a _.chain pour ça non ?"

const getTodaysMeal = _.pipe(
  _.filter({healthy: false}),
  _.shuffle,
  _.find(_.pipe(
   _.property('amount'),
   _.gt(_, 10)
  )),
  _.property('name'),
  _.toUpper
);

console.log(getTodaysMeal(foods));
// => FISH AND CHIPS
function getTodaysMeal(foods) {
  return _.chain(foods)
    .filter({healthy: false})
    .shuffle()
    .find(function(item) {
      return item.amount > 10;
    })
    .property('name')
    .toUpper()
    .value();
}

console.log(getTodaysMeal(foods));
// => FISH AND CHIPS

Les problèmes avec _.chain

  • Obligation de finir par `.value()`. Parfois...
  • Focus sur les données versus focus sur les opérations
  • Pour réutiliser, il faut encapsuler dans une fonction
  • Injection de fonctions custom pas pratique (_.mixin/.thru)  
  • Extraction en fonction plus compliquée
  • Why using `_.chain` is a mistake, by Izaak Schroeder

Extraction de fonctions

const shuffleUnhealthyFood = _.flow(
  _.filter({healthy: false}),
  _.shuffle,
);

const shoutName = _.flow(
  _.property('name'),
  _.toUpper
);

const getTodaysMeal = _.pipe(
  shuffleUnhealthyFood,
  _.find(_.pipe(
   _.property('amount'),
   _.gt(_, 10)
  )),
  shoutName
);

console.log(getTodaysMeal(foods));
// => FISH AND CHIPS
function shuffleUnhealthyFood(foods) {
  return foods
    .filter({healthy: false})
    .shuffle();
}

function shoutName(food) {
  return _.toUpper(food.name);
}

function getTodaysMeal(foods) {
  return shuffleUnhealthyFood(_.chain(foods))
    .find(function(item) {
      return item.amount > 10;
    })
    .thru(shoutName)
    .value();
}

console.log(getTodaysMeal(foods));
// => FISH AND CHIPS

Outils

eslint-plugin-lodash-fp

  • Pas de _.chain()
  • Pas d'arguments en trop
  • Pas de fonction encapsulante inutile
  • Pas de variables dans les iteratee en trop
_.get('a.b.c', 'defaultValue', {a: 2});



_.filter(function(x) { return _.get('a.b.c', x); }, array);


_.map(function iteratee(value, index) {
  return value * index;
}, array);

n_ --fp

Merci :)

Introduction à Lodash/fp

By Jeroen Engels

Introduction à Lodash/fp

  • 893