Skip to content

Instantly share code, notes, and snippets.

@jfhector
Created March 6, 2022 14:22
Show Gist options
  • Save jfhector/dd0639112a2dd82c59015a9f94ba79d0 to your computer and use it in GitHub Desktop.
Save jfhector/dd0639112a2dd82c59015a9f94ba79d0 to your computer and use it in GitHub Desktop.
Implementing simple Monads

Implementing simple Monads

From the book "Mastering Functional Programming"

const VALUE = Symbol('value');
class Container {
constructor(x) {
this[VALUE] = x;
}
static of(x) {
return new Container(x);
}
map(fn) {
return fn(this[VALUE]);
}
toString() {
return `${this.constructor.name} wrapping ${this[VALUE]}`
}
valueOf() {
return this[VALUE];
}
}
// *****************
class Functor extends Container {
static of(x) {
return new Functor(x);
}
map(fn) {
return Functor.of(fn(this[VALUE]));
}
}
// *****************
class Nothing extends Functor {
isNothing() {
return true;
}
toString() {
return "Nothing()";
}
map(_) {
return this;
}
orElse(v) {
return v;
}
}
class Just extends Functor {
static of(x) {
return new Just(x);
}
isNothing() {
return false;
}
map(fn) {
return Maybe.of(fn(this[VALUE]));
}
orElse(_) {
return this.valueOf();
}
}
class Maybe extends Functor {
constructor(x) {
return (x === undefined || x === null) ? // !!
new Nothing()
: new Just(x);
}
static of(x) {
return new Maybe(x);
}
}
// *****************
class Monad extends Functor {
static of(x) {
return new Monad(x);
}
map(fn) {
return Monad.of(fn(this[VALUE]));
}
flatten() {
const value = this[VALUE];
return (value instanceof Container) ? value.flatten() : this; // !! Note we're returning `this` (the container), not the unwrapped value
}
chain(fn) {
return this.map(fn).flatten();
}
}
// *****************
// TODO: Recreate Nothing, Just and Maybe so they extend Monad rather than Functor, so they have flatten and chain
// *****************
class Left extends Monad {
isLeft() {
return true;
}
map(_) {
return this;
}
}
class Right extends Monad {
isLeft() {
return false;
}
map(fn) {
return Either.of(null, fn(this[VALUE]));
}
}
class Either extends Monad {
constructor(left, right) {
return right == undefined ? // !! If right is neither null nor undefined, return a new Right (regardless of left)
new Left(left)
: new Right(right);
}
static of(left, right) {
return new Either(left, right);
}
}
// *****************
class Try extends Either {
constructor(thunk, message) {
try {
return new Either(null, thunk());
} catch (err) {
return new Either(message ?? err, null)
}
}
static of (thunk, message) {
return new Try(thunk, message);
}
}
// *****************
const cont1 = Container.of(42);
const funct1 = Functor.of(42);
const just1 = Just.of(42);
const maybe1 = Maybe.of(42);
const maybe2 = Maybe.of(null);
const monad1 = Monad.of(42);
const monadOfMonad1 = Monad.of(monad1);
const either1 = Either.of(null, 42)
const either2 = Either.of(new Error("Some error"), 42)
const either3 = Either.of(new Error("Some error"), null)
const try1 = Try.of(() => 42);
const try2 = Try.of(() => {throw new Error("Oh shit")});
const try3 = Try.of(() => {throw new Error("Oh shit")}, "Custom error message");
console.log(
// cont1.toString() // Container wrapping 42
// cont1.valueOf() // 42
// cont1.map(x => x*2) // 84
// funct1.toString() // Functor wrapping 42
// funct1.map(x => x*2) // Functor { [Symbol(value)]: 84 }
// just1 // Just { [Symbol(value)]: 42 }
// maybe1 // Just { [Symbol(value)]: 42 }
// maybe2 // Nothing { [Symbol(value)]: undefined }
// maybe1.map(x => x * 2) // Just { [Symbol(value)]: 84 }
// maybe1.map(_ => null) // Nothing { [Symbol(value)]: undefined }
// maybe1.orElse("Some default or placeholder value") // 42
// maybe2.orElse("Some default or placeholder value") // Some default or placeholder value
// monad1 // Monad { [Symbol(value)]: 42 }
// monadOfMonad1 // Monad { [Symbol(value)]: Monad { [Symbol(value)]: 42 } }
// monadOfMonad1.flatten() // Monad { [Symbol(value)]: 42 }
// monadOfMonad1.chain(x => x * 2) // Monad { [Symbol(value)]: 84 }
// either1 // Right { [Symbol(value)]: 42 }
// either2 // Right { [Symbol(value)]: 42 }
// either3 // Left {
// [Symbol(value)]: Error: Some error
// at Object.<anonymous> (/Users/jfhector/Desktop/continuation_test/monads/monads.js:153:27)
// at Module._compile (internal/modules/cjs/loader.js:1072:14)
// at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)
// at Module.load (internal/modules/cjs/loader.js:937:32)
// at Function.Module._load (internal/modules/cjs/loader.js:778:12)
// at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
// at internal/main/run_main_module.js:17:47
// }
// ^ NOTE: This error wasn't thrown
// either1.map(x => x * 2) // Right { [Symbol(value)]: 84 }
// either3.map(x => x * 2) // Left {
// [Symbol(value)]: Error: Some error
// at Object.<anonymous> (/Users/jfhector/Desktop/continuation_test/monads/monads.js:153:27)
// at Module._compile (internal/modules/cjs/loader.js:1072:14)
// at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)
// at Module.load (internal/modules/cjs/loader.js:937:32)
// at Function.Module._load (internal/modules/cjs/loader.js:778:12)
// at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
// at internal/main/run_main_module.js:17:47
// }
// ^ NOTE: This error wasn't thrown
// either2.chain(x => x * 2) // Right { [Symbol(value)]: 84 }
// try1 // Right { [Symbol(value)]: 42 }
// try2 // Left {
// [Symbol(value)]: Error: Oh shit
// at /Users/jfhector/Desktop/continuation_test/monads/monads.js:171:34
// at new Try (/Users/jfhector/Desktop/continuation_test/monads/monads.js:147:31)
// at Function.of (/Users/jfhector/Desktop/continuation_test/monads/monads.js:154:12)
// at Object.<anonymous> (/Users/jfhector/Desktop/continuation_test/monads/monads.js:171:18)
// at Module._compile (internal/modules/cjs/loader.js:1072:14)
// at Object.Module._extensions..js (internal/modules/cjs/loader.js:1101:10)
// at Module.load (internal/modules/cjs/loader.js:937:32)
// at Function.Module._load (internal/modules/cjs/loader.js:778:12)
// at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:76:12)
// at internal/main/run_main_module.js:17:47
// }
// ^ NOTE: This error wasn't thrown
// try3 // Left { [Symbol(value)]: 'Custom error message' }
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment