-
-
Save leihuang23/b6aba7993d414776ed01839da416ba6b to your computer and use it in GitHub Desktop.
Functional JavaScript Monad Classes - (Maybe Just Nothing) - (Either Left Right) (IOMonad) and my type checking utils
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
import is from './is-util'; | |
/** | |
* Either Monad class (from Functional Programming in JavaScript) | |
*/ | |
class Either { | |
constructor(value) { | |
this._value = value; | |
} | |
get value () { | |
return this._value; | |
} | |
static fromNullable (a) { | |
// Book doesn't check if a is undefined. Am I wrong to check for undefined? | |
return !is.defined(a) || is.none(a) ? Either.left(a) : Either.right(a); | |
} | |
static left (a) { | |
return new Left(a); | |
} | |
static right (a) { | |
return new Right(a); | |
} | |
static of(a) { | |
return Either.right(a); | |
} | |
} | |
/* | |
* Left class is meant to handle errors or possible unexpected results. | |
* (Left is a bit similar to Nothing) | |
*/ | |
class Left extends Either { | |
constructor(unexpectedResult) { | |
super(unexpectedResult); | |
} | |
map (fn) { | |
return this; | |
} | |
get value () { | |
throw new TypeError(`Can't extract value from a Left() monad`); | |
} | |
getOrElse (other) { | |
return other; | |
} | |
orElse (fn) { | |
return fn(this._value); | |
} | |
getOrElseThrow (a) { | |
throw new Error(a); | |
} | |
filter (fn) { | |
return this; | |
} | |
get isNothing () { | |
return true; | |
} | |
toString () { | |
return `[object Either.Left] (value: ${this._value})`; | |
} | |
} | |
/** | |
* Right monad used for handling valid/expected types of results | |
*/ | |
class Right extends Either { | |
constructor (value) { | |
super(value); | |
} | |
get value () { | |
return this._value; | |
} | |
getOrElse () { | |
return this.value; | |
} | |
orElse () { | |
return this; | |
} | |
getOrElseThrow (a) { | |
return this.value; | |
} | |
chain (fn) { | |
return fn(this.value); | |
} | |
filter (fn) { | |
return Either.fromNullable(fn(this.value) ? this.value : null); | |
} | |
toString() { | |
return `[object Either.Right] (value: ${this._value})`; | |
} | |
} |
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
import is from './is-util'; | |
/** | |
* The IOMonad class (from Functional Programming in JavaScript) | |
*/ | |
export class IOMonad { | |
constructor (effect) { | |
if ( !is.callable(effect) ) { | |
throw new Error(`IOMonad expects a callable effect! Saw ${typeof effect}`); | |
} | |
this.effect = effect; | |
} | |
static of (a) { | |
return new IOMonad( () => a ); | |
} | |
static from (fn) { | |
return new IOMonad( fn ); | |
} | |
map (fn) { | |
return new IOMonad( () => fn(this.effect()) ); | |
} | |
chain (fn) { | |
return fn( this.effect ); | |
} | |
run () { | |
return this.effect(); | |
} | |
} |
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
import is from './is-util'; | |
/** | |
* This is the Maybe monad class (from Functional Programming in JavaScript). | |
* (Errata found in code has been fixed) | |
* Factory `Maybe.fromNullable` lifts a value into either a `Just` | |
* or a `Nothing` monad class. | |
*/ | |
class Maybe { | |
constructor() {} | |
static fromNullable (a) { | |
return !is.defined(a) || is.none(a) ? Maybe.nothing() : Maybe.just(a); | |
} | |
static nothing() { | |
return new Nothing(); | |
} | |
static just(a) { | |
return new Just(a); | |
} | |
static of(a) { | |
return Maybe.just(a); | |
} | |
get isNothing() { | |
return false; | |
} | |
get isJust() { | |
return false; | |
} | |
} | |
/* | |
* Nothing class wraps undefined or null values and prevents errors | |
* that otherwise occur when mapping unexpected undefined or null | |
* values | |
*/ | |
class Nothing extends Maybe { | |
get value () { | |
throw new TypeError(`Can't extract value from Nothing`); | |
} | |
getOrElse (other) { | |
return other; | |
} | |
map (fn) { | |
return this; | |
} | |
filter () { | |
return this.value; | |
} | |
get isNothing () { | |
return true; | |
} | |
toString () { | |
return `[object Maybe.Nothing] (value: ${this._value})`; | |
} | |
} | |
/** | |
* Monad Just used for valid values | |
*/ | |
class Just extends Maybe { | |
constructor (value) { | |
super(...arguments); | |
this._value = value; | |
} | |
get value () { | |
return this._value; | |
} | |
getOrElse () { | |
return this.value; | |
} | |
map (fn) { | |
return Maybe.of(fn(this.value)); | |
} | |
filter (fn) { | |
return Maybe.fromNullable(fn(this.value) ? this.value : null); | |
} | |
get isJust () { | |
return true; | |
} | |
toString() { | |
return `[object Maybe.Just] (value: ${this._value})`; | |
} | |
} |
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
/** | |
* Base Wrapper Monad class (from Functional Programming in JavaScript) | |
* (Errata found in code has been fixed) | |
*/ | |
class Wrapper { | |
constructor (value) { | |
this._value = value; | |
} | |
static of (a) { | |
return new Wrapper(a); | |
} | |
map (f) { | |
return Wrapper.of( f(this._value) ); | |
} | |
join () { | |
return this._value instanceof Wrapper ? this.value.join() : this; | |
} | |
toString () { | |
return `[object Wrapper] (value: ${this._value})` | |
} | |
} |
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
/** | |
* Utilities for testing values for type | |
* ``` | |
* if ( is.none(val) ) { "Value is null" } | |
* if ( is.object(val) ) { "Value is a real object" } | |
* if ( is.objectType(val) ) { "Value is of type object" } | |
* if ( is.array(val) ) { "Value is an Array" } | |
* if ( is.callable(val) ) { "Value is a Function" } | |
* if ( is.defined(val) ) { "Value is not undefined" } | |
* if ( is.string(val) ) { "Value is a String" } | |
* if ( is.number(val) ) { "Value is a number" } | |
* if ( is.a(val, B) ) { "Value is of type B" } | |
* if ( is.thenable(val) ) { "Value is has then function" } | |
* ``` | |
*/ | |
export default class is { | |
static none(testValue) { | |
return !testValue && typeof testValue === "object"; | |
} | |
static objectType(testValue) { | |
return !!testValue && typeof testValue === "object"; | |
} | |
static object(testValue) { | |
return is.objectType(testValue) && testValue.constructor !== Array; | |
} | |
static array(testValue) { | |
return is.objectType(testValue) && testValue.constructor === Array; | |
} | |
static callable(testValue) { | |
return typeof testValue === "function"; | |
} | |
static defined(testValue) { | |
return typeof testValue !== "undefined"; | |
} | |
static string(testValue) { | |
return typeof testValue === "string"; | |
} | |
static number(testValue) { | |
return typeof testValue === "number" && !Number.isNaN(testValue); | |
} | |
// Class | |
static a(testValue, typeConstructor) { | |
return (is.objectType(testValue) || is.callable(testValue)) && testValue.constructor === typeConstructor; | |
} | |
// Promise | |
static thenable(testValue) { | |
return is.objectType(testValue) && is.callable(testValue.then); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment