CHAININGCHAINING ETET COMPOSITIONCOMPOSITION DEDE
FONCTIONS AVEC _FONCTIONS AVEC _
@NICOESPEON@NICOESPEON
http://nicoespeon.com
AVANT DEAVANT DE PLONGERPLONGER
> PETIT CHECK-UP> PETIT CHECK-UP
QUAND JE DISQUAND JE DIS __
Personnellement j'utilise (v3.0+)
Ça marche aussi avec (pour l'essentiel) o/
lodash
underscore
FUNCTIONAL PROGRAMMING?FUNCTIONAL PROGRAMMING?
First-class functions
Higher order functions
Pure functions
Recursion
Immutability
≠ imperative programming
mais !≠ OOP
CHAININGCHAINING DE FONCTIONSDE FONCTIONS
> ET POURQUOI FAIRE ?> ET POURQUOI FAIRE ?
CAS CONCRET : OPÉRATIONS MULTIPLES (0)CAS CONCRET : OPÉRATIONS MULTIPLES (0)
this.set( "items", [
{ id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 },
null,
{ id: "ebaaa82e", cepage: "gamay", type: "grape", quantity: 2 },
{ id: "ee2bcc12", cepage: "viognier", type: "grape", quantity: 0 }
] );
CAS CONCRET : OPÉRATIONS MULTIPLES (1)CAS CONCRET : OPÉRATIONS MULTIPLES (1)
function getItems() {
return _.compact( this.get( "items" ) );
}
getItems();
// =>
// [
// { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 },
// { id: "ebaaa82e", cepage: "gamay", type: "grape", quantity: 2 },
// { id: "ee2bcc12", cepage: "viognier", type: "grape", quantity: 0 }
// ]
CAS CONCRET : OPÉRATIONS MULTIPLES (2)CAS CONCRET : OPÉRATIONS MULTIPLES (2)
function getItems() {
return _.reject( _.compact( this.get( "items" ) ), { quantity: 0 } );
}
getItems();
// =>
// [
// { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 },
// { id: "ebaaa82e", cepage: "gamay", type: "grape", quantity: 2 }
// ]
CAS CONCRET : OPÉRATIONS MULTIPLES (3)CAS CONCRET : OPÉRATIONS MULTIPLES (3)
function getItems() {
return _.filter(
_.reject(
_.compact( this.get( "items" ) ),
{ quantity: 0 }
),
{ type: "juice" }
);
}
getItems();
// =>
// [
// { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 }
// ]
CAS CONCRET : OPÉRATIONS MULTIPLES (3)CAS CONCRET : OPÉRATIONS MULTIPLES (3)
// Better, really?
function getItems() {
var compactedItems = _.compact( this.get( "items" ) );
var positiveCompactedItems = _.reject( compactedItems, { quantity: 0 } );
return _.filter( positiveCompactedItems, { type: "juice" } );
}
getItems();
// =>
// [
// { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 }
// ]
_.CHAIN(_.CHAIN( VALUEVALUE ))
Creates a lodash object that wraps value with explicit
method chaining enabled.
_.chain( this.get( "items" ) );
// => returns `LodashWrapper`
_.chain( this.get( "items" ) ).compact();
// <=> `_.compact( this.get( "items" ) );`
// BUT… returns `LodashWrapper` too!
// And so we can do ->
_.chain( this.get( "items" ) )
.compact()
.reject( { quantity: 0 } )
.filter( { type: "juice" } )
// …
.map( doSomething );
_.CHAIN(_.CHAIN( VALUEVALUE )) …… .VALUE().VALUE() !!
Executes the chained sequence to extract the
unwrapped value.
_.chain( this.get( "items" ) )
.compact()
.reject( { quantity: 0 } )
.filter( { type: "juice" } )
// Hum… still return `LodashWrapper` >_<
.value();
// And voilà o/
// =>
// [
// { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 }
// ]
CAS CONCRET : OPÉRATIONS MULTIPLES (4)CAS CONCRET : OPÉRATIONS MULTIPLES (4)
function getItems() {
return _.chain( this.get( "items" ) )
.compact()
.reject( { quantity: 0 } )
.filter( { type: "juice" } )
.value();
}
getItems();
// =>
// [
// { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 }
// ]
function getItems() {
return _( this.get( "items" ) ) // _( value ) === _.chain( value )
.compact()
.reject( { quantity: 0 } )
.filter( { type: "juice" } )
.value();
}
getItems();
// =>
// [
// { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 }
// ]
TOUT ÇA POURTOUT ÇA POUR ÇAÇA ??
> INTÉRÊTS DU CHAINING> INTÉRÊTS DU CHAINING
PIPELINE / FLOWPIPELINE / FLOW
function getItems() {
return _( this.get( "items" ) )
.compact()
.reject( isEmpty )
.filter( isJuice )
.map( parseText )
// … we construct the pipeline
// flow is clear, readable!
.value();
}
Ça vous rappelle quelque chose ?
PIPELINE / FLOWPIPELINE / FLOW
function makeItemAvailable( userID, index ) {
return _findOneItem( userID, index )
.then( doSomethingClever )
.then( updateStatusAs( "available" ) )
.then( res.ok )
.catch( res.serverError );
}
// You get the same idea with promises.
function getBottles( options ) {
// Ensure default options.
options = _.defaults( {}, options, { isAppellationOnly: false } );
var bottlesWrapper = _( this.get( "bottles" ) ).map( parseText );
// …
// Dynamically build the pipeline.
if( options.isAppellationOnly ) {
bottlesWrapper = bottlesWrapper.pick( [ "appellation" ] );
}
// Nothing have been computed so far!
return bottlesWrapper.value(); // evaluates when needed only!
}
function getParsedBottlesWrapper() {
return _( this.get( "bottles" ) ).map( parseText );
}
function getBottles( options ) {
// Ensure default options.
options = _.defaults( {}, options, { isAppellationOnly: false } );
var bottlesWrapper = getParsedBottlesWrapper.call( this );
// Dynamically build the pipeline.
if( options.isAppellationOnly ) {
bottlesWrapper = bottlesWrapper.pick( [ "appellation" ] );
}
// Nothing have been computed so far!
return bottlesWrapper.value(); // evaluates when needed only!
}
LAZYLAZY EVALUATIONEVALUATION
LAZYLAZY EVALUATIONEVALUATION
Pour en savoir plus >
http://filimanjaro.com/blog/2014/introducing-lazy-evaluation/
COMPOSITIONCOMPOSITION ET AUTRES RUSESET AUTRES RUSES
> POUR CONSTRUIRE DES PIPELINES> POUR CONSTRUIRE DES PIPELINES EFFICACESEFFICACES
COMPOSITION ?COMPOSITION ?
(f ⋅ g)(x) = f(g(x))
function add10( value ) { // f
return 10 + value;
}
function times3( value ) { // g
return 3 * value;
}
add10( times3( 10 ) ); // (f ∘ g)( 10 )
// => 10 + ( 3 * 10 )
// => 40
COMPOSITION ?COMPOSITION ?
function add10( value ) { // f
return 10 + value;
}
function times3( value ) { // g
return 3 * value;
}
var times3AndAdd10 = _.compose( add10, times3 ); // f ∘ g
times3AndAdd10( 10 );
// => 40
times3AndAdd10( 0 );
// => 10
function add10( value ) { // f
return 10 + value;
}
function times3( value ) { // g
return 3 * value;
}
var times3AndAdd10 = _.flowRight( add10, times3 ); // f ∘ g
times3AndAdd10( 10 );
// => 40
times3AndAdd10( 0 );
// => 10
_.FLOWRIGHT(_.FLOWRIGHT( [FUNCS][FUNCS] ))
Crée une fonction qui retourne le résultat des funcs où
chacune est invoquée avec le résultat de la fonction qui la
précède, de la droite vers la gauche (= compose).
_.FLOW(_.FLOW( [FUNCS][FUNCS] ))
function add10( value ) { // f
return 10 + value;
}
function times3( value ) { // g
return 3 * value;
}
var times3AndAdd10 = _.flow( times3, add10 ); // f ∘ g
times3AndAdd10( 10 );
// => 40
times3AndAdd10( 0 );
// => 10
Si _.flowRight n'est pas intuitif pour vous.
APPLICATION PARTIELLE :APPLICATION PARTIELLE : _.PARTIAL()_.PARTIAL()
function greet( greeting, name ) {
return greeting + " " + name;
}
var sayHelloTo = _.partial( greet, "Hello" );
// returns a function with params partially set.
sayHelloTo( "Backbone" );
// → "Hello Backbone"
APPLICATION PARTIELLEAPPLICATION PARTIELLE
function _isCepageInRecipe( cepage, bottle ) { … }
function _areBuildingsPartOfRecipe( buildings, bottle ) { … }
function hasMatchingBottles( cepage, buildings ) {
var isCepageInRecipe = _.partial( _isCepageInRecipe, cepage );
var areBuildingsPartOfRecipe = _.partial( _areBuildingsPartOfRecipe, buildings );
return _( this.get( "bottles" ) )
.filter( isCepageInRecipe )
.any( areBuildingsPartOfRecipe );
}
Pour pouvoir chaîner dans la vraie vie…
function greet( greeting, name ) {
return greeting + " " + name;
}
var greetBackbone = _.partialRight( greet, "Backbone" );
// returns a function with params partially set.
greetBackbone( "Hello" );
// → "Hello Backbone"
_.PARTIALRIGHT()_.PARTIALRIGHT()
// Not so smart params order here…
function _isCepageInRecipe( bottle, cepage ) { … }
function _areBuildingsPartOfRecipe( bottle, buildings ) { … }
// Not so smart params order here…
function _isCepageInRecipe( bottle, cepage ) { … }
function _areBuildingsPartOfRecipe( bottle, buildings ) { … }
function hasMatchingBottles( cepage, buildings ) {
// Use `_` as a placeholder for not-yet-defined params!
var isCepageInRecipe = _.partial( _isCepageInRecipe, _, cepage );
var areBuildingsPartOfRecipe = _.partial( _areBuildingsPartOfRecipe, _, buildings );
return _( this.get( "bottles" ) )
.filter( isCepageInRecipe )
.any( areBuildingsPartOfRecipe );
}
// Not so smart params order here…
function _isCepageInRecipe( bottle, cepage ) { … }
function _areBuildingsPartOfRecipe( bottle, buildings ) { … }
function hasMatchingBottles( cepage, buildings ) {
// Use `_` as a placeholder for not-yet-defined params!
var isCepageInRecipe = _.partialRight( _isCepageInRecipe, cepage );
var areBuildingsPartOfRecipe = _.partialRight( _areBuildingsPartOfRecipe, buildings );
return _( this.get( "bottles" ) )
.filter( isCepageInRecipe )
.any( areBuildingsPartOfRecipe );
}
APPLICATION PARTIELLE : LE COUTEAU SUISSEAPPLICATION PARTIELLE : LE COUTEAU SUISSE
COMPOSITIONCOMPOSITION VS.VS. CHAINING ?CHAINING ?
_.flow est un outil pour créer des higher order functions
Peut éventuellement remplacer des chaînes simples…
… mais pas toujours adapté pour remplacer _.chain
function getJuices( items ) {
return _( items )
.compact()
.reject( { quantity: 0 } )
.filter( { type: "juice" } )
.value();
}
// Flow equivalent
var getJuices = _.flow(
_.partialRight( _.filter, { type: "juice" } ),
_.partialRight( _.reject, { quantity: 0 } ),
_.compact
);
BUT WAIT, THERE'SBUT WAIT, THERE'S MOREMORE
> QUELQUES> QUELQUES SUBTILITÉSSUBTILITÉS ET GRANDS CLASSIQUESET GRANDS CLASSIQUES
TOUT N'ESTTOUT N'EST PASPAS CHAINABLECHAINABLE
Il y a des méthodes qui le sont : _.keys, _.map, _.push,
_.pluck, _.union, …
D'autres qui ne le sont pas (par défaut) : _.find,
_.isNumber, _.reduce, _.sum, …
MÉTHODES NON-CHAINABLESMÉTHODES NON-CHAINABLES
function getJuiceTotalQuantity() {
return _( this.get( "items" ) )
.compact()
.filter( isJuice )
.pluck( "quantity" )
.sum();
// => return the sum
// no need for `.value()` -> implicitly called
}
Plus d'infos sur la doc > https://lodash.com/docs#_
_.PROTOTYPE.PLANT(_.PROTOTYPE.PLANT( VALUEVALUE ))
Crée un clone de la chaîne avec la value donnée
var wrapper = _( [ 1, 2, null, 3 ] ).compact();
var otherWrapper = wrapper.plant( [ "a", null, "b", undefined ] );
wrapper.value();
// => [ 1, 2, 3 ]
otherWrapper.value();
// => [ "a", "b" ]
_.PROTOTYPE.COMMIT()_.PROTOTYPE.COMMIT()
Exécute la chaîne et retourne un wrapper.
var array = [ 1, 2, 3 ];
var wrapper = _( array ).push( 2 );
console.log( array );
// => [ 1, 2, 3 ]
// Nothing executed, nothing changed.
wrapper = wrapper.commit();
console.log( array );
// => [ 1, 2, 3, 2 ]
// Chain executed
// `_.push()` mutated the original `array`
wrapper.without( 2 ).value();
// => [ 1, 3 ]
_.TAP(_.TAP( VALUE, INTERCEPTOR, [THISARG]VALUE, INTERCEPTOR, [THISARG] ))
Invoque interceptor et retourne value.
_( [ 1, 2, null, 3 ] )
.compact()
.tap( function ( value ) {
console.log( "tapped ->", value );
} )
.push( 1 )
.value();
// => "tapped -> [ 1, 2, 3 ]"
// => "[ 1, 2, 3, 1 ]"
"Tap" dans la chaîne = très utile pour debug !
_.TAP(_.TAP( VALUE, INTERCEPTOR, [THISARG]VALUE, INTERCEPTOR, [THISARG] ))
Pour log une valeur intermédiaire
_( [ 1, 2, null, 3 ] )
.compact()
// Can use `console.log`, just don't forget to bind the context!
.tap( console.log.bind( console ) )
.push( 1 )
.value();
// => "[ 1, 2, 3 ]"
// => "[ 1, 2, 3, 1 ]"
_.THRU(_.THRU( VALUE, INTERCEPTOR, [THISARG]VALUE, INTERCEPTOR, [THISARG] ))
Invoque interceptor et retourne la value de l'interceptor.
_( [ 1, 2, null, 3 ] )
.compact()
.thru( function ( value ) {
console.log( "tapped ->", value );
// Don't forget to return a value for the chain.
return value.reverse();
} )
.push( 0 )
.value();
// => "tapped -> [ 1, 2, 3 ]"
// => "[ 3, 2, 1, 0 ]"
MERCI !MERCI ! DES QUESTIONS ?DES QUESTIONS ?

Chaining et composition de fonctions avec lodash / underscore

  • 1.
    CHAININGCHAINING ETET COMPOSITIONCOMPOSITIONDEDE FONCTIONS AVEC _FONCTIONS AVEC _
  • 2.
  • 3.
    AVANT DEAVANT DEPLONGERPLONGER > PETIT CHECK-UP> PETIT CHECK-UP
  • 4.
    QUAND JE DISQUANDJE DIS __ Personnellement j'utilise (v3.0+) Ça marche aussi avec (pour l'essentiel) o/ lodash underscore
  • 5.
    FUNCTIONAL PROGRAMMING?FUNCTIONAL PROGRAMMING? First-classfunctions Higher order functions Pure functions Recursion Immutability ≠ imperative programming mais !≠ OOP
  • 6.
    CHAININGCHAINING DE FONCTIONSDEFONCTIONS > ET POURQUOI FAIRE ?> ET POURQUOI FAIRE ?
  • 7.
    CAS CONCRET :OPÉRATIONS MULTIPLES (0)CAS CONCRET : OPÉRATIONS MULTIPLES (0) this.set( "items", [ { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 }, null, { id: "ebaaa82e", cepage: "gamay", type: "grape", quantity: 2 }, { id: "ee2bcc12", cepage: "viognier", type: "grape", quantity: 0 } ] );
  • 8.
    CAS CONCRET :OPÉRATIONS MULTIPLES (1)CAS CONCRET : OPÉRATIONS MULTIPLES (1) function getItems() { return _.compact( this.get( "items" ) ); } getItems(); // => // [ // { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 }, // { id: "ebaaa82e", cepage: "gamay", type: "grape", quantity: 2 }, // { id: "ee2bcc12", cepage: "viognier", type: "grape", quantity: 0 } // ]
  • 9.
    CAS CONCRET :OPÉRATIONS MULTIPLES (2)CAS CONCRET : OPÉRATIONS MULTIPLES (2) function getItems() { return _.reject( _.compact( this.get( "items" ) ), { quantity: 0 } ); } getItems(); // => // [ // { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 }, // { id: "ebaaa82e", cepage: "gamay", type: "grape", quantity: 2 } // ]
  • 10.
    CAS CONCRET :OPÉRATIONS MULTIPLES (3)CAS CONCRET : OPÉRATIONS MULTIPLES (3) function getItems() { return _.filter( _.reject( _.compact( this.get( "items" ) ), { quantity: 0 } ), { type: "juice" } ); } getItems(); // => // [ // { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 } // ]
  • 11.
    CAS CONCRET :OPÉRATIONS MULTIPLES (3)CAS CONCRET : OPÉRATIONS MULTIPLES (3) // Better, really? function getItems() { var compactedItems = _.compact( this.get( "items" ) ); var positiveCompactedItems = _.reject( compactedItems, { quantity: 0 } ); return _.filter( positiveCompactedItems, { type: "juice" } ); } getItems(); // => // [ // { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 } // ]
  • 12.
    _.CHAIN(_.CHAIN( VALUEVALUE )) Createsa lodash object that wraps value with explicit method chaining enabled. _.chain( this.get( "items" ) ); // => returns `LodashWrapper` _.chain( this.get( "items" ) ).compact(); // <=> `_.compact( this.get( "items" ) );` // BUT… returns `LodashWrapper` too! // And so we can do -> _.chain( this.get( "items" ) ) .compact() .reject( { quantity: 0 } ) .filter( { type: "juice" } ) // … .map( doSomething );
  • 13.
    _.CHAIN(_.CHAIN( VALUEVALUE ))…… .VALUE().VALUE() !! Executes the chained sequence to extract the unwrapped value. _.chain( this.get( "items" ) ) .compact() .reject( { quantity: 0 } ) .filter( { type: "juice" } ) // Hum… still return `LodashWrapper` >_< .value(); // And voilà o/ // => // [ // { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 } // ]
  • 14.
    CAS CONCRET :OPÉRATIONS MULTIPLES (4)CAS CONCRET : OPÉRATIONS MULTIPLES (4) function getItems() { return _.chain( this.get( "items" ) ) .compact() .reject( { quantity: 0 } ) .filter( { type: "juice" } ) .value(); } getItems(); // => // [ // { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 } // ] function getItems() { return _( this.get( "items" ) ) // _( value ) === _.chain( value ) .compact() .reject( { quantity: 0 } ) .filter( { type: "juice" } ) .value(); } getItems(); // => // [ // { id: "ae213aa4", cepage: "merlot", type: "juice", quantity: 3 } // ]
  • 15.
    TOUT ÇA POURTOUTÇA POUR ÇAÇA ?? > INTÉRÊTS DU CHAINING> INTÉRÊTS DU CHAINING
  • 16.
    PIPELINE / FLOWPIPELINE/ FLOW function getItems() { return _( this.get( "items" ) ) .compact() .reject( isEmpty ) .filter( isJuice ) .map( parseText ) // … we construct the pipeline // flow is clear, readable! .value(); } Ça vous rappelle quelque chose ?
  • 17.
    PIPELINE / FLOWPIPELINE/ FLOW function makeItemAvailable( userID, index ) { return _findOneItem( userID, index ) .then( doSomethingClever ) .then( updateStatusAs( "available" ) ) .then( res.ok ) .catch( res.serverError ); } // You get the same idea with promises.
  • 18.
    function getBottles( options) { // Ensure default options. options = _.defaults( {}, options, { isAppellationOnly: false } ); var bottlesWrapper = _( this.get( "bottles" ) ).map( parseText ); // … // Dynamically build the pipeline. if( options.isAppellationOnly ) { bottlesWrapper = bottlesWrapper.pick( [ "appellation" ] ); } // Nothing have been computed so far! return bottlesWrapper.value(); // evaluates when needed only! } function getParsedBottlesWrapper() { return _( this.get( "bottles" ) ).map( parseText ); } function getBottles( options ) { // Ensure default options. options = _.defaults( {}, options, { isAppellationOnly: false } ); var bottlesWrapper = getParsedBottlesWrapper.call( this ); // Dynamically build the pipeline. if( options.isAppellationOnly ) { bottlesWrapper = bottlesWrapper.pick( [ "appellation" ] ); } // Nothing have been computed so far! return bottlesWrapper.value(); // evaluates when needed only! } LAZYLAZY EVALUATIONEVALUATION
  • 19.
    LAZYLAZY EVALUATIONEVALUATION Pour ensavoir plus > http://filimanjaro.com/blog/2014/introducing-lazy-evaluation/
  • 20.
    COMPOSITIONCOMPOSITION ET AUTRESRUSESET AUTRES RUSES > POUR CONSTRUIRE DES PIPELINES> POUR CONSTRUIRE DES PIPELINES EFFICACESEFFICACES
  • 21.
    COMPOSITION ?COMPOSITION ? (f⋅ g)(x) = f(g(x)) function add10( value ) { // f return 10 + value; } function times3( value ) { // g return 3 * value; } add10( times3( 10 ) ); // (f ∘ g)( 10 ) // => 10 + ( 3 * 10 ) // => 40
  • 22.
    COMPOSITION ?COMPOSITION ? functionadd10( value ) { // f return 10 + value; } function times3( value ) { // g return 3 * value; } var times3AndAdd10 = _.compose( add10, times3 ); // f ∘ g times3AndAdd10( 10 ); // => 40 times3AndAdd10( 0 ); // => 10 function add10( value ) { // f return 10 + value; } function times3( value ) { // g return 3 * value; } var times3AndAdd10 = _.flowRight( add10, times3 ); // f ∘ g times3AndAdd10( 10 ); // => 40 times3AndAdd10( 0 ); // => 10
  • 23.
    _.FLOWRIGHT(_.FLOWRIGHT( [FUNCS][FUNCS] )) Créeune fonction qui retourne le résultat des funcs où chacune est invoquée avec le résultat de la fonction qui la précède, de la droite vers la gauche (= compose).
  • 24.
    _.FLOW(_.FLOW( [FUNCS][FUNCS] )) functionadd10( value ) { // f return 10 + value; } function times3( value ) { // g return 3 * value; } var times3AndAdd10 = _.flow( times3, add10 ); // f ∘ g times3AndAdd10( 10 ); // => 40 times3AndAdd10( 0 ); // => 10 Si _.flowRight n'est pas intuitif pour vous.
  • 25.
    APPLICATION PARTIELLE :APPLICATIONPARTIELLE : _.PARTIAL()_.PARTIAL() function greet( greeting, name ) { return greeting + " " + name; } var sayHelloTo = _.partial( greet, "Hello" ); // returns a function with params partially set. sayHelloTo( "Backbone" ); // → "Hello Backbone"
  • 26.
    APPLICATION PARTIELLEAPPLICATION PARTIELLE function_isCepageInRecipe( cepage, bottle ) { … } function _areBuildingsPartOfRecipe( buildings, bottle ) { … } function hasMatchingBottles( cepage, buildings ) { var isCepageInRecipe = _.partial( _isCepageInRecipe, cepage ); var areBuildingsPartOfRecipe = _.partial( _areBuildingsPartOfRecipe, buildings ); return _( this.get( "bottles" ) ) .filter( isCepageInRecipe ) .any( areBuildingsPartOfRecipe ); } Pour pouvoir chaîner dans la vraie vie…
  • 27.
    function greet( greeting,name ) { return greeting + " " + name; } var greetBackbone = _.partialRight( greet, "Backbone" ); // returns a function with params partially set. greetBackbone( "Hello" ); // → "Hello Backbone" _.PARTIALRIGHT()_.PARTIALRIGHT()
  • 28.
    // Not sosmart params order here… function _isCepageInRecipe( bottle, cepage ) { … } function _areBuildingsPartOfRecipe( bottle, buildings ) { … } // Not so smart params order here… function _isCepageInRecipe( bottle, cepage ) { … } function _areBuildingsPartOfRecipe( bottle, buildings ) { … } function hasMatchingBottles( cepage, buildings ) { // Use `_` as a placeholder for not-yet-defined params! var isCepageInRecipe = _.partial( _isCepageInRecipe, _, cepage ); var areBuildingsPartOfRecipe = _.partial( _areBuildingsPartOfRecipe, _, buildings ); return _( this.get( "bottles" ) ) .filter( isCepageInRecipe ) .any( areBuildingsPartOfRecipe ); } // Not so smart params order here… function _isCepageInRecipe( bottle, cepage ) { … } function _areBuildingsPartOfRecipe( bottle, buildings ) { … } function hasMatchingBottles( cepage, buildings ) { // Use `_` as a placeholder for not-yet-defined params! var isCepageInRecipe = _.partialRight( _isCepageInRecipe, cepage ); var areBuildingsPartOfRecipe = _.partialRight( _areBuildingsPartOfRecipe, buildings ); return _( this.get( "bottles" ) ) .filter( isCepageInRecipe ) .any( areBuildingsPartOfRecipe ); } APPLICATION PARTIELLE : LE COUTEAU SUISSEAPPLICATION PARTIELLE : LE COUTEAU SUISSE
  • 29.
    COMPOSITIONCOMPOSITION VS.VS. CHAINING?CHAINING ? _.flow est un outil pour créer des higher order functions Peut éventuellement remplacer des chaînes simples… … mais pas toujours adapté pour remplacer _.chain function getJuices( items ) { return _( items ) .compact() .reject( { quantity: 0 } ) .filter( { type: "juice" } ) .value(); } // Flow equivalent var getJuices = _.flow( _.partialRight( _.filter, { type: "juice" } ), _.partialRight( _.reject, { quantity: 0 } ), _.compact );
  • 30.
    BUT WAIT, THERE'SBUTWAIT, THERE'S MOREMORE > QUELQUES> QUELQUES SUBTILITÉSSUBTILITÉS ET GRANDS CLASSIQUESET GRANDS CLASSIQUES
  • 31.
    TOUT N'ESTTOUT N'ESTPASPAS CHAINABLECHAINABLE Il y a des méthodes qui le sont : _.keys, _.map, _.push, _.pluck, _.union, … D'autres qui ne le sont pas (par défaut) : _.find, _.isNumber, _.reduce, _.sum, …
  • 32.
    MÉTHODES NON-CHAINABLESMÉTHODES NON-CHAINABLES functiongetJuiceTotalQuantity() { return _( this.get( "items" ) ) .compact() .filter( isJuice ) .pluck( "quantity" ) .sum(); // => return the sum // no need for `.value()` -> implicitly called } Plus d'infos sur la doc > https://lodash.com/docs#_
  • 33.
    _.PROTOTYPE.PLANT(_.PROTOTYPE.PLANT( VALUEVALUE )) Créeun clone de la chaîne avec la value donnée var wrapper = _( [ 1, 2, null, 3 ] ).compact(); var otherWrapper = wrapper.plant( [ "a", null, "b", undefined ] ); wrapper.value(); // => [ 1, 2, 3 ] otherWrapper.value(); // => [ "a", "b" ]
  • 34.
    _.PROTOTYPE.COMMIT()_.PROTOTYPE.COMMIT() Exécute la chaîneet retourne un wrapper. var array = [ 1, 2, 3 ]; var wrapper = _( array ).push( 2 ); console.log( array ); // => [ 1, 2, 3 ] // Nothing executed, nothing changed. wrapper = wrapper.commit(); console.log( array ); // => [ 1, 2, 3, 2 ] // Chain executed // `_.push()` mutated the original `array` wrapper.without( 2 ).value(); // => [ 1, 3 ]
  • 35.
    _.TAP(_.TAP( VALUE, INTERCEPTOR,[THISARG]VALUE, INTERCEPTOR, [THISARG] )) Invoque interceptor et retourne value. _( [ 1, 2, null, 3 ] ) .compact() .tap( function ( value ) { console.log( "tapped ->", value ); } ) .push( 1 ) .value(); // => "tapped -> [ 1, 2, 3 ]" // => "[ 1, 2, 3, 1 ]" "Tap" dans la chaîne = très utile pour debug !
  • 36.
    _.TAP(_.TAP( VALUE, INTERCEPTOR,[THISARG]VALUE, INTERCEPTOR, [THISARG] )) Pour log une valeur intermédiaire _( [ 1, 2, null, 3 ] ) .compact() // Can use `console.log`, just don't forget to bind the context! .tap( console.log.bind( console ) ) .push( 1 ) .value(); // => "[ 1, 2, 3 ]" // => "[ 1, 2, 3, 1 ]"
  • 37.
    _.THRU(_.THRU( VALUE, INTERCEPTOR,[THISARG]VALUE, INTERCEPTOR, [THISARG] )) Invoque interceptor et retourne la value de l'interceptor. _( [ 1, 2, null, 3 ] ) .compact() .thru( function ( value ) { console.log( "tapped ->", value ); // Don't forget to return a value for the chain. return value.reverse(); } ) .push( 0 ) .value(); // => "tapped -> [ 1, 2, 3 ]" // => "[ 3, 2, 1, 0 ]"
  • 38.
    MERCI !MERCI !DES QUESTIONS ?DES QUESTIONS ?