Last active
December 9, 2017 01:22
-
-
Save pinyin/8261bfb6ae363a779040cd742eaebdd9 to your computer and use it in GitHub Desktop.
A Typescript Maybe implementation with Mocha and Chai tests
This file contains hidden or 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
export function isDefined<T>(value: T | undefined | null | void): value is T { | |
return typeof value !== 'undefined' && value !== null | |
} |
This file contains hidden or 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 {Maybe} from './Maybe' | |
describe('Maybe', ()=> { | |
const nothing = Maybe.Nothing | |
const just = Maybe.Just(1) | |
describe('Nothing', ()=> { | |
it('should not be exist', ()=> { | |
expect(nothing.exists).to.equal(false) | |
}) | |
it('should equal another nothing', ()=> { | |
expect(nothing).to.equal(Maybe.Nothing) | |
}) | |
}) | |
describe('Just', ()=> { | |
it('should be existing', ()=> { | |
expect(just.exists).to.equal(true) | |
}) | |
it('should return contained value', ()=> { | |
expect(just['value']).to.equal(1) | |
}) | |
}) | |
describe('#equals', ()=> { | |
it('should not be equal to another Maybe with different containing status', ()=> { | |
expect(nothing.equals(just)).to.be.false | |
}) | |
it('should be equal to another empty Maybe when empty', ()=> { | |
expect(nothing.equals(Maybe.Nothing)).to.be.true | |
}) | |
it('should be equal to another Maybe with the same content', ()=> { | |
expect(just.equals(Maybe.Just(1))).to.be.true | |
}) | |
}) | |
describe('#map', ()=> { | |
it('should map to nothing when empty', ()=> { | |
expect(nothing.map(value=> value).equals(Maybe.Nothing)).to.be.true | |
}) | |
it('should map to function value when has value', ()=> { | |
expect(just.map(value=> value).equals(Maybe.Just(1))).to.be.true | |
}) | |
}) | |
describe('#flatMap', ()=> { | |
it('should flatMap to nothing when nothing', ()=> { | |
expect(nothing.flatMap(value=> Maybe.Just(value))).to.equal(Maybe.Nothing) | |
}) | |
it('should flatMap to flatten function result when has value', ()=> { | |
expect(just.flatMap(value=> Maybe.Just(value))).to.deep.equal(Maybe.Just(1)) | |
}) | |
}) | |
describe('#or', ()=> { | |
it('should return alternate value when empty', ()=> { | |
expect(nothing.or(1)).to.equal(1) | |
}) | |
it('should return origin value when not empty', ()=> { | |
expect(just.or(2)).to.equal(1) | |
}) | |
}) | |
describe('#orMaybe', ()=> { | |
it('should return alternate Maybe when empty', ()=> { | |
expect(nothing.orMaybe(Maybe.Just(1)).equals(Maybe.Just(1))).to.be.true | |
}) | |
it('should return origin value when not empty', ()=> { | |
expect(just.orMaybe(Maybe.Just(2)).equals(Maybe.Just(1))).to.be.true | |
}) | |
}) | |
}) |
This file contains hidden or 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 {isDefined} from './isDefined' | |
export type Maybe<T> = Maybe.Just<T> | Maybe.Nothing<T> | |
export namespace Maybe { | |
export type Just<T> = { readonly exists: true, readonly value: T } & Methods<T> | |
export type Nothing<T> = { readonly exists: false } & Methods<T> | |
type Methods<T> = { | |
equals(maybe: Maybe<T>): boolean | |
map<R=T>(func: (value: T) => R): Maybe<R> | |
flatMap<R=T>(func: (value: T) => Maybe<R>): Maybe<R> | |
or(value: T): T | |
orMaybe(maybe: Maybe<T>): Maybe<T> | |
} | |
class Maybe_<T> implements Methods<T> { | |
equals(maybe: Maybe<T>, compare?: (a: T, b: T)=> boolean): boolean { | |
if(this.exists != maybe.exists) { | |
return false | |
} | |
if(!this.exists || !maybe.exists) { | |
return true | |
} | |
return isDefined(compare) | |
? compare(this.value, maybe.value) | |
: this.value === maybe.value | |
} | |
readonly exists: boolean | |
readonly value: T | |
map<R=T>(func: (value: T) => R): Maybe<R> { | |
return this.exists | |
? new Maybe_(func(this.value)) as Just<R> | |
: new Maybe_<R>(undefined) as Nothing<R> | |
} | |
or(defaultValue: T): T { | |
return this.exists ? this.value : defaultValue | |
} | |
orMaybe(another: Maybe<T>): Maybe<T> { | |
return this.exists ? this as Maybe<T> : another | |
} | |
flatMap<R=T>(func: (value: T) => Maybe<R>): Maybe<R> { | |
if (this.exists) { | |
const result_ = func(this.value) | |
if (result_.exists) { | |
return Maybe.Just(result_.value) | |
} | |
} | |
return Maybe.Nothing | |
} | |
constructor(value: T | undefined) { | |
if (isDefined(value)) { | |
this.value = value | |
} | |
this.exists = isDefined(value) | |
} | |
} | |
export function Just<T>(value: T): Just<T> { | |
return new Maybe_(value) as Just<T> | |
} | |
export const Nothing = new Maybe_(undefined) as Nothing<any> | |
export function Lift<T>(value: T | undefined | null): Maybe<T> { | |
return new Maybe_(value) as Maybe<T> | |
} | |
export function all<A, B>(a: Maybe<A>, b: Maybe<B>): Maybe<[A, B]> | |
export function all<A, B, C>(a: Maybe<A>, b: Maybe<B>, c: Maybe<C>): Maybe<[A, B, C]> | |
// TODO others | |
export function all<T>(...maybes: Array<Maybe<any>>): Maybe<Array<any>> { | |
return maybes.some(maybe => !maybe.exists) | |
? Maybe.Nothing | |
: Maybe.Just(maybes.map((maybe: Maybe.Just<any>) => maybe.value)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment