From 8713a9d730bbfecbdac352ea5ca50b6f007798d9 Mon Sep 17 00:00:00 2001 From: "andy.patterson" Date: Fri, 27 Apr 2018 11:41:51 -0400 Subject: [PATCH] feat: comply with fantasy-land specs By complying with fantasy-land specifications we can maximize the reusability of this lib across other libraries. The spec itself is reasonably straight-forward, we just need to expose a few methods with an alias. Because fantasy-land doesn't expose types, it isn't currently possible to import their definitions for use. closes: #5 --- src/fantasy-land.ts | 6 ++++++ src/index.ts | 4 ++++ src/maybe.ts | 12 ++++++++++++ tests/maybe.test.ts | 13 +++++++++++++ 4 files changed, 35 insertions(+) create mode 100644 src/fantasy-land.ts diff --git a/src/fantasy-land.ts b/src/fantasy-land.ts new file mode 100644 index 0000000..73f49aa --- /dev/null +++ b/src/fantasy-land.ts @@ -0,0 +1,6 @@ +// TODO: remove this if fantasy-land exports own types + +export const map = 'fantasy-land/map'; +export const ap = 'fantasy-land/ap'; +export const of = 'fantasy-land/of'; +export const chain = 'fantasy-land/chain'; diff --git a/src/index.ts b/src/index.ts index 56c85a0..9344749 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import Maybe, { Nil, isNothing } from './maybe'; +import * as fl from './fantasy-land'; import { some } from './some'; import { none } from './none'; @@ -6,6 +7,9 @@ export const maybe = (value: T | Nil): Maybe => isNothing(value) ? none() : some(value); +// gotta do this here to get around circular imports +Maybe.of = maybe; +Maybe[fl.of] = maybe; export { some } from './some'; export { none } from './none'; diff --git a/src/maybe.ts b/src/maybe.ts index f96803e..2656aa5 100644 --- a/src/maybe.ts +++ b/src/maybe.ts @@ -1,3 +1,7 @@ +import * as fl from './fantasy-land'; + +export const binder = (context: any, f: T): T => f.bind(context); // tslint:disable-line ban-types + export type Nil = null | undefined; export interface MatchType { @@ -12,6 +16,8 @@ export const isNothing = (thing: any): thing is Nil => { export default abstract class Maybe { protected constructor(protected value: T | Nil) {} + static of: (x: T) => Maybe; + isNothing() { return isNothing(this.value); } abstract expect(msg?: string | Error): T; @@ -22,4 +28,10 @@ export default abstract class Maybe { abstract or(other: Maybe | (() => Maybe)): Maybe; abstract eq(other: Maybe): boolean; abstract asNullable(): T | null; + + // Fantasy-land aliases + static [fl.of]: (x: T) => Maybe; + [fl.map] = binder(this, this.map); + [fl.chain] = binder(this, this.flatMap); + [fl.ap]: (m: Maybe<(x: T) => U>) => Maybe = m => m.flatMap(f => this.map(f)); } diff --git a/tests/maybe.test.ts b/tests/maybe.test.ts index a5bbd02..7372ea9 100644 --- a/tests/maybe.test.ts +++ b/tests/maybe.test.ts @@ -234,3 +234,16 @@ test('eq - some is `eq` to some if the contents are ===', () => { // Not same object, not ==== expect(some(x).eq(some({}))).toBe(false); }); + +// ------- +// Fantasy +// ------- + +test('fantasy-land/map - calls into the map method', () => { + expect.assertions(1); + + const value = "i'm not nil"; + const definitely = some(value); + + definitely["fantasy-land/map"](v => expect(v).toBe(value)); +});