Created
October 30, 2017 21:04
-
-
Save andrewb/f5a6d0dee433db9329e7a417bf0d37c1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Hello! Let's try something different... | |
// ************************* | |
// *** Functional Mixins *** | |
// ************************* | |
// PART 1 - Classes and Mixins | |
// Before we get to functional mixins, let's take a look at mixins, and before we get to mixins let's | |
// look at classes. | |
class Bird { | |
chirp() { | |
return 'cheep'; | |
} | |
fly() { | |
return 'flap flap'; | |
} | |
} | |
class Penguin extends Bird { | |
constructor() { | |
super(); | |
} | |
swim() { | |
return 'paddle paddle'; | |
} | |
} | |
const instance = new Penguin(); | |
// console.log(instance); | |
// console.log(Object.getPrototypeOf(instance) === Penguin.prototype); | |
// console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(instance))); | |
// console.log(Object.getPrototypeOf(Object.getPrototypeOf(instance)) === Bird.prototype); | |
// console.log(instance.chirp()); | |
// console.log(instance.swim()); | |
// console.log(instance.fly()); // uh-oh | |
// - Methods become non-enumerable properties | |
// - Tight coupling | |
// - The ol' gorilla and banana problem (You wanted a banana but what you got was a gorilla holding | |
// the banana and the entire jungle) | |
// Let's convert it to use mixins. | |
const chirp = { | |
chirp() { | |
return 'cheep'; | |
} | |
}; | |
const fly = { | |
fly() { | |
return 'flap flap'; | |
} | |
}; | |
const swim = { | |
swim() { | |
return 'paddle paddle'; | |
} | |
}; | |
const mixin = Object.assign({}, chirp, swim); // Or you could { ...chirp, ...swim } if you prefer | |
// console.log(mixin); | |
// console.log(mixin.chirp()); | |
// console.log(mixin.swim()); | |
// console.log(mixin.fly()); | |
// So that's it in a nutshell. | |
// - Class inheritance uses a base class and you inherit everything in that base class whether you want | |
// it or not. A strict taxonomy. | |
// - A mixin is a composite object composed from smaller feature based components. | |
// The big difference is philosophical. With mixins you’re inheriting features. We think in terms of "has a", | |
// instead of "is a". So, rather than think "I want an object that is an A", "I want an object that has the | |
// features of A". | |
// DISCLAIMER - This might not be true, but... | |
// The term mixin is an ice cream analogy. Start with vanilla, add some chocolate, nuts, whatever. You can pick | |
// and choose what you want. The same as building up composite objects from smaller component parts. | |
// This basic approach is great for SIMPLE objects. But what if we need state... | |
// Part 2 - Functional Mixins | |
// Sometimes we want objects that have some kind of ENCAPSULATED state. You can use functional mixins when you | |
// want state and data privacy. | |
// How do we encapsulate? Closures! | |
const flying = obj => { | |
let isFlying = false; | |
// Compose... | |
return Object.assign({}, obj, { | |
fly() { | |
// This inner function can access the variables | |
// in the outer function, i.e. a closure | |
isFlying = true; | |
return this; // Just so we can chain... | |
}, | |
land() { | |
// Note, we're not using => here. We want `this` to refer to the object, | |
// not lexical this. | |
isFlying = false; | |
return this; // Just so we can chain... | |
}, | |
isFlying() { | |
return isFlying; | |
} | |
}); | |
}; | |
const bird = flying({ name: 'chickadee' }); | |
// console.log(bird); | |
// console.log(bird.fly().isFlying()); | |
// console.log(bird.land().isFlying()); | |
// We now have a flying feature. What if we want a duck... | |
// First we need a way to compose our functions together | |
// We can do this with functional composition. We could spend a lot of time talking about this, | |
// but the tl;dr is that it chains together functions to create a new function. | |
// f composed with g | |
// (f . g)(x) = f(g(x)) | |
// Let's use pipe | |
// const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x); | |
const pipe = (...fns) => { | |
// Use a rest param to convert the arguments to an array... | |
// fns = [function flying, function anon] | |
return x => { | |
// x = { name: 'duck' } | |
// Here we call each fn, piping the result so we end up with a single value | |
// E.g. anon(flying({ name: 'duck' })) | |
// In this case the single value is: | |
/* | |
{ | |
name: "duck", | |
fly: function fly, | |
land: function land, | |
isFlying: function isFlying, | |
quack: function quack | |
} | |
*/ | |
return fns.reduce((y, f) => f(y), x); | |
} | |
} | |
const quacking = (quack = 'Quack') => obj => Object.assign({}, obj, { | |
quack: () => quack | |
}); | |
const createDuck = (quack) => pipe( | |
flying, | |
quacking(quack) // currying | |
)({ name: 'duck' }); | |
const duck = createDuck('Quaaack'); | |
// console.log(duck); | |
// console.log(duck.fly().quack()); | |
// console.log(duck.isFlying()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment