Skip to content

Instantly share code, notes, and snippets.

@pinyin
Last active December 9, 2017 01:22
Show Gist options
  • Save pinyin/8261bfb6ae363a779040cd742eaebdd9 to your computer and use it in GitHub Desktop.
Save pinyin/8261bfb6ae363a779040cd742eaebdd9 to your computer and use it in GitHub Desktop.
A Typescript Maybe implementation with Mocha and Chai tests
export function isDefined<T>(value: T | undefined | null | void): value is T {
return typeof value !== 'undefined' && value !== null
}
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
})
})
})
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