footer: © 2015-2017 Reginald Braithwaite. Some rights reserved. slidenumbers: true
^ https://www.flickr.com/photos/faceme/7759225036
^ https://www.flickr.com/photos/faceme/7759225036
^ https://www.flickr.com/photos/mariannebevis/8636118515
^ https://www.flickr.com/photos/mariannebevis/8636118515
^ http://www.wikiart.org/en/piet-mondrian/avond-evening-the-red-tree-1910
^ "Making responsibilities and relationships explicit"
^ http://www.wikiart.org/en/piet-mondrian/avond-evening-the-red-tree-1910
^ https://www.flickr.com/photos/npobre/2601582256
^ https://www.flickr.com/photos/westher/7709055816
^ https://www.flickr.com/photos/westher/7709055816
We decompose entities to make discreet responsibilities explicit
Parse.User.logIn("user", "pass", {
success: function (user) {
query.find({
success: function (users) {
users[0].save({ key: value }, {
success: function (user) {
currentUser = user;
}
});
}
});
}
});
^ Adapted from http://blog.parse.com/learn/engineering/whats-so-great-about-javascript-promises/
let assignCurrentUser = (user) => { currentUser = user; };
let saveFirstUser = (users) =>
users[0].save({ key: value }, {
success: assignCurrentUser
});
let logUserIn = (user) =>
query.find({
success: saveFirstUser
});
Parse.User.logIn("user", "pass", { success: logUserIn });
^ Adapted from http://blog.parse.com/learn/engineering/whats-so-great-about-javascript-promises/
^ https://www.flickr.com/photos/mariannebevis/8646014265
^ https://www.flickr.com/photos/mariannebevis/8646014265
We compose entities to make the relationships between them explicit
let findUser = (user) => query.find();
let saveFirstUser = (user) => users[0].save({ key: value });
let assignCurrentUser = (user) => { currentUser = user; };
Parse.User.logIn("user", "pass")
.then(findUser)
.then(saveFirstUser)
.then(assignCurrentUser);
^ Adapted from http://blog.parse.com/learn/engineering/whats-so-great-about-javascript-promises/
^ https://www.flickr.com/photos/jforth/4467184640
^ https://www.flickr.com/photos/jforth/4467184640
^ https://www.flickr.com/photos/jforth/4467184640
Back to decomposition
^ https://www.flickr.com/photos/guysie/3325635275
^ https://www.flickr.com/photos/guysie/3325635275
Let's look at something else
^ https://www.flickr.com/photos/130973380@N08/17185509447
^ "pluck"
pluck
: "A convenient version of what is perhaps the most common use-case for map
, extracting a list of property values."
^ https://www.flickr.com/photos/130973380@N08/17185509447
^ underscorejs.org
let pluck = (collection, property) =>
collection.map( (obj) => obj[property] );
var deStijl = [
{name: 'Theo van Doesburg', occupation: 'theorist'},
{name: 'Piet Mondriaan', occupation: 'painter'},
{name: 'Gerrit Rietveld', occupation: 'architect'}];
pluck(deStijl, 'name')
//=> ["Theo van Doesburg", "Piet Mondriaan", "Gerrit Rietveld"]
var deStijl = [
{name: 'Theo van Doesburg', occupation: 'theorist'},
{name: 'Piet Mondriaan', occupation: 'painter'},
{name: 'Gerrit Rietveld', occupation: 'architect'}];
let pluckFrom = (collection) =>
(property) => pluck(collection, property);
pluckFrom(deStijl)('name')
//=> ["Theo van Doesburg", "Piet Mondriaan", "Gerrit Rietveld"]
let pluckWith = (property) =>
(collection) => pluck(collection, property);
pluckWith('name')(deStijl)
//=> ["Theo van Doesburg", "Piet Mondriaan", "Gerrit Rietveld"]
pluckFrom
andpluckWith
partially applypluck
^ https://www.flickr.com/photos/16210667@N02/18067981830
^ https://www.flickr.com/photos/16210667@N02/18067981830
^ https://www.flickr.com/photos/vauvau/23067022785
^ https://www.flickr.com/photos/vauvau/23067022785
A decorator is a higher-order function that takes a function, and returns another function that adds to or modifies its argument's behaviour.
let pluck = (collection, property) =>
collection.map( (obj) => obj[property] );
// decomposes into:
let pluckFrom = (collection) =>
(property) => pluck(collection, property);
^ http://raganwald.com/2013/03/07/currying-and-partial-application.html
let pluckFrom = (collection) =>
(property) => pluck(collection, property);
// extract `pluck`:
let ____ = (pluck, collection) =>
(property) => pluck(collection, property);
// rename:
let leftApply = (fn, a) =>
(b) => fn(a, b);
let pluckFrom = (collection) =>
leftApply(pluck, collection);
// again:
let pluckFrom = leftApply(leftApply, pluck);
// again
let pluckFrom = leftApply(leftApply, leftApply)(pluck);
^ https://www.flickr.com/photos/saramarlowe/8170948596
^ https://www.flickr.com/photos/saramarlowe/8170948596
let rightApply = (fn, b) =>
(a) => fn(a, b);
let pluckWith = (property) => (pluck, property);
// again:
let pluckWith = leftApply(rightApply, pluck);
// again:
let pluckWith = leftApply(leftApply, rightApply)(pluck);
// leftApply(leftApply, leftApply)
let Istarstar = (a) => (b) => (c) => a(b, c);
let pluckFrom = Istarstar(pluck);
// leftApply(leftApply, rightApply)
let C = (a) => (b) => (c) => a(c, b)
let pluckWith = C(pluck);
^ "cardinal"
let get = (object, property) =>
object[property];
get({name: 'Gerrit Rietveld'}, 'name')
//=> Gerrit Rietveld
let getWith = C(get);
let nameOf = getWith('name');
nameOf({name: 'Gerrit Rietveld'})
//=> Gerrit Rietveld
^ http://www.panoramio.com/photo/82281274
^ http://www.panoramio.com/photo/82281274
let map = (collection, fn) =>
collection.map(fn);
let mapWith = C(map);
let namesOf = mapWith(nameOf);
^ https://www.flickr.com/photos/dalbera/20059027984
^ https://www.flickr.com/photos/dalbera/20059027984
Back to composition
^ before we go, now you know what to say when somebody asks "What good is currying?" or "What is the practical purpose of partial application?" It's to decompose functions by responsibility.
^ https://www.flickr.com/photos/ctaweb/8487304182
^ "Simple Composition"
^ https://www.flickr.com/photos/ctaweb/8487304182
let compose = (a, b) =>
(c) => a(b(c));
^ 'compose'. B = (a) => (b) => (c) => a(b(c))
var deStijl = [
{name: 'Theo van Doesburg', occupation: 'theorist'},
{name: 'Piet Mondriaan', occupation: 'painter'},
{name: 'Gerrit Rietveld', occupation: 'architect'}];
let pluckWith = compose(mapWith, getWith);
let namesOf = pluckWith('name');
namesOf(deStijl)
//=> ["Theo van Doesburg","Piet Mondriaan","Gerrit Rietveld"]
pluckWith = compose(mapWith, getWith);
^ compose
wires the output of one function into the input of another
^ https://www.flickr.com/photos/72283508@N00/2444938101
^ "We compose entities to make the relationships between them explicit"
^ and now we know how to answer that question, too!
^ https://www.flickr.com/photos/72283508@N00/2444938101
More Composition
let mix = (...ingredients) =>
console.log('mixing', ...ingredients);
let bake = () => console.log('baking');
let cool = () => console.log('cooling');
let makeBread = (...ingredients) => {
mix(...ingredients);
bake();
cool();
}
let before = (fn, decoration) =>
(...args) => {
decoration(...args);
return fn(...args);
};
let bakeBread = before(bake, mix);
let makeBread = (...ingredients) => {
bakeBread();
cool();
}
before
makes the time relationship between two functions explicit
let after = (fn, decoration) =>
(...args) => {
let returnValue = fn(...args);
decoration(...args);
return returnValue;
};
let bakeBread = before(bake, mix);
let makeBread = after(bakeBread, cool);
after
also makes the time relationship between two functions explicit
let beforeWith = (decoration) =>
rightApply(before, decoration);
let mixBefore = beforeWith(mix);
let bakeBread = mixBefore(bake);
let afterWith = (decoration) =>
rightApply(after, decoration);
let coolAfter = afterWith(after);
let makeBread = coolAfter(bakeBread);
beforeWith
andafterWith
are combinators that turn functions into decorators that compose behaviour
^ "These are not the whole story, by far."
^ https://www.flickr.com/photos/lightplay/4727131891
^ https://www.flickr.com/photos/lightplay/4727131891
^ http://raganwald.com/2015/03/12/symmetry.html
let before = (fn, decoration) =>
function (...args) {
decoration.apply(this, args);
return fn.apply(this, args);
};
let after = (fn, decoration) =>
function (...args) {
let returnValue = fn.apply(this, args);
decoration.apply(this, args);
return returnValue;
};
Why coloured decorators matter
class Bread {
constructor (...ingredients) {
this.ingredients = ingredients;
}
mix () {
console.log('mixing', ...this.ingredients);
};
bake () { console.log('baking'); }
cool () { console.log('cooling'); }
}
class Bread {
// ...
make () {
this.mix();
this.bake();
this.cool();
}
}
Classes can be decorated too
const decorateMethodWith = (decorator, ...methodNames) =>
(clazz) => {
for (let methodName of methodNames) {
const method = clazz.prototype[methodName];
Object.defineProperty(clazz.prototype, methodName, {
value: decorator(method),
writable: true
});
}
return clazz;
};
const beforeAll = (decorator, ...methodNames) =>
decorateMethodWith((method) => before(method, decorator), ...methodNames),
afterAll = (decorator, ...methodNames) =>
decorateMethodWith((method) => after(method, decorator), ...methodNames);
let invoke = (methodName) => function (...args) {
return this[methodName](...args);
}
let BetterBread =
beforeAll(invoke('mix'), 'make')(
afterAll(invoke('cool'), 'make')(
class {
// ...
make () {
this.bake();
}
}
)
);
^ https://www.flickr.com/photos/68112440@N07/6210847796
^ "Looking Forward"
^ https://www.flickr.com/photos/68112440@N07/6210847796
ES.who-knows-when
let invoke = (methodName) => function (...args) {
return this[methodName](...args);
}
@beforeAll(invoke('mix'), 'make')
@afterAll(invoke('cool'), 'make')
class AwesomeBread {
// ...
make () {
this.bake();
}
}
let methodDecorator = (decorator) =>
function (target, name, descriptor) {
descriptor.value = decorator(descriptor.value);
}
let invokeBefore = (methodName) =>
methodDecorator( (methodBody) =>
before(methodBody, invoke(methodName))
);
let invokeAfter = (methodName) =>
methodDecorator( (methodBody) =>
after(methodBody, invoke(methodName))
);
class Bread {
// ...
@invokeBefore('mix')
@invokeAfter('cool')
make () {
this.bake();
}
}
^ https://www.flickr.com/photos/hogeslag/3404068564
^ https://www.flickr.com/photos/hogeslag/3404068564
- Extract function
- Promise interface
- Partial application
- Extract closed-over binding
(more!)
- Simple composition
- Composition decorators
- Class decorators
- Method decorators
Don't worry about the details!
^ "Decorators declutter secondary concerns"
^ https://en.wikipedia.org/wiki/Theo_van_Doesburg
^ "Composition makes relationships explicit"
^ https://en.wikipedia.org/wiki/Theo_van_Doesburg
These ideas matter
There are only two hard problems in Computer Science: Cache invalidation, and naming things.
^ Phil Karlton
^ https://www.flickr.com/photos/michale/2744016741
^ "Naming entities is hard because you have to figure out which entities need to be named"
^ https://www.flickr.com/photos/michale/2744016741
^ https://www.flickr.com/photos/stretta/5572576057
^ "Naming relationships is hard because you have to figure out which relationships need to be named"
^ https://www.flickr.com/photos/stretta/5572576057
^ https://www.flickr.com/photos/cristiano_betta/2970086666
^ https://www.flickr.com/photos/cristiano_betta/2970086666
^ https://www.flickr.com/photos/joao_trindade/4362414729
^ "Combinators give us a language for naming things"
^ https://www.flickr.com/photos/joao_trindade/4362414729
^ https://www.flickr.com/photos/suburbanbloke/723665503
^ "Do not follow in the footsteps of the sages: Seek what they sought"
^ https://www.flickr.com/photos/suburbanbloke/723665503
^ https://www.flickr.com/photos/suburbanbloke/723665503