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)
- What is functional programming, par Kris Jenkins
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
- 971