Skip to content

Instantly share code, notes, and snippets.

@andrewb
Created October 30, 2017 21:04
Show Gist options
  • Save andrewb/f5a6d0dee433db9329e7a417bf0d37c1 to your computer and use it in GitHub Desktop.
Save andrewb/f5a6d0dee433db9329e7a417bf0d37c1 to your computer and use it in GitHub Desktop.
// 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