Composing Software: The Book
"Composing Software", the hit blog post series on functional programming and software composition in JavaScript is now a best selling book on Leanpub. Also available in print. On February 18th, 2017...
https://medium.com/javascript-scene/composing-software-the-book-f31c77fc3ddc
Il semble que la perfection soit atteinte non quand il n'y a plus rien à ajouter, mais quand il n'y a plus rien à retrancher. Saint-Exupéry
Curry
javascriptconst pipe = (...fns) => x => fns.reduce((y, f) => f(y), x); const trace = label => value => { console.log(`${ label }: ${ value }`); return value; }; const g = n => n + 1; const f = n => n * 2;/* Now the function application order runs top-to-bottom: */ const h = pipe( g, trace('after g'), f, trace('after f'), );h(20); /* after g: 21 after f: 42 */
This trace function is beautiful.
At first you don't understand it well and then, everything is clear.
This curried (one argument at a time) with data last (the value) enables the "point free" call to this function that we see in the pipe.
Here is what it would have looked like with multiple arguments
javascriptconst pipe = (...fns) => x => fns.reduce((y, f) => f(y), x); const trace = (label, value) => { console.log(`${ label }: ${ value }`); return value; }; const g = n => n + 1; const f = n => n * 2; const h = pipe( g, // the trace() calls are no longer point-free, // introducing the intermediary variable, `x`. x => trace('after g', x), f, x => trace('after f', x), ); h(20);
Function with multiple fat arrows seem difficult to grasp when they are actually extremely easy to read.
In our case of two fat arrows, if you apply a first argument (here the label), it returns a function, if you give this function a new argument it executes its definition
"Functions in a pipeline must expect exactly one argument."
javascript// Tiny, recursive autocurry const curry = ( f, arr = [] ) => (...args) => ( a => a.length === f.length ? f(...a) : curry(f, a) )([...arr, ...args]);
Functional mixins
javascriptconst flying = o => { let isFlying = false; return Object.assign({}, o, { fly () { isFlying = true; return this; }, isFlying: () => isFlying, land () { isFlying = false; return this; } }); }; const bird = flying({}); console.log( bird.isFlying() ); // false console.log( bird.fly().isFlying() ); // true const quacking = quack => o => Object.assign({}, o, { quack: () => quack }); const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x); const createDuck = quack => pipe( flying, quacking(quack) )({}); const duck = createDuck('Quack!'); console.log(duck.fly().quack());
I use and recommend HOCs (Higher Order Components) with function composition to compose UI components.
Use the simplest practical implementation. Start on the left and move to the right only as needed: pure functions > factories > functional mixins > classes.
Factory functions in ES6
javascriptconst withConstructor = constructor => o => ({ // create the delegate [[Prototype]] __proto__: { // add the constructor prop to the new [[Prototype]] constructor }, // mix all o's props into the new object ...o }); const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x); // or `import pipe from 'lodash/fp/flow';` // Set up some functional mixins const withFlying = o => { let isFlying = false; return { ...o, fly () { isFlying = true; return this; }, land () { isFlying = false; return this; }, isFlying: () => isFlying } }; const withBattery = ({ capacity }) => o => { let percentCharged = 100; return { ...o, draw (percent) { const remaining = percentCharged - percent; percentCharged = remaining > 0 ? remaining : 0; return this; }, getCharge: () => percentCharged, getCapacity: () => capacity }; }; const createDrone = ({ capacity = '3000mAh' }) => pipe( withFlying, withBattery({ capacity }), withConstructor(createDrone) )({}); const myDrone = createDrone({ capacity: '5500mAh' }); console.log(` can fly: ${ myDrone.fly().isFlying() === true } can land: ${ myDrone.land().isFlying() === false } battery capacity: ${ myDrone.getCapacity() } battery status: ${ myDrone.draw(50).getCharge() }% battery drained: ${ myDrone.draw(75).getCharge() }% remaining `); console.log(` constructor linked: ${ myDrone.constructor === createDrone } `);
Problems with class
Compare the class:
javascriptclass User { constructor ({userName, avatar}) { this.userName = userName; this.avatar = avatar; } }const currentUser = new User({ userName: 'Foo', avatar: 'foo.png' });
Vs the equivalent factory…
javascriptconst createUser = ({ userName, avatar }) => ({ userName, avatar }); const currentUser = createUser({ userName: 'Foo', avatar: 'foo.png' });
Composable data types
javascriptconst t = value => { const add = n => t(value + n); return Object.assign(add, { toString: () => `t(${ value })`, valueOf: () => value }); };
Algebraic Data Types
Functor and categories
A functor data type is something you can map over. It’s a container which has an interface which can be used to apply a function to the values inside it. When you see a functor, you should think “mappable”. Functor types are typically represented as an object with a
.map()
method that maps from inputs to outputs while preserving structure. In practice, “preserving structure” means that the return value is the same type of functor (though values inside the container may be a different type).