Skip to content

Commit

Permalink
feat: comply with fantasy-land specs
Browse files Browse the repository at this point in the history
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
  • Loading branch information
andy.patterson authored and andnp committed Apr 29, 2018
1 parent 0aa2553 commit 8713a9d
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/fantasy-land.ts
Original file line number Diff line number Diff line change
@@ -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';
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import Maybe, { Nil, isNothing } from './maybe';
import * as fl from './fantasy-land';
import { some } from './some';
import { none } from './none';

export const maybe = <T>(value: T | Nil): Maybe<T> => 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';
Expand Down
12 changes: 12 additions & 0 deletions src/maybe.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import * as fl from './fantasy-land';

export const binder = <T extends Function>(context: any, f: T): T => f.bind(context); // tslint:disable-line ban-types

export type Nil = null | undefined;

export interface MatchType<T, R> {
Expand All @@ -12,6 +16,8 @@ export const isNothing = (thing: any): thing is Nil => {
export default abstract class Maybe<T> {
protected constructor(protected value: T | Nil) {}

static of: <T>(x: T) => Maybe<T>;

isNothing() { return isNothing(this.value); }

abstract expect(msg?: string | Error): T;
Expand All @@ -22,4 +28,10 @@ export default abstract class Maybe<T> {
abstract or<U>(other: Maybe<U> | (() => Maybe<U>)): Maybe<T | U>;
abstract eq(other: Maybe<T>): boolean;
abstract asNullable(): T | null;

// Fantasy-land aliases
static [fl.of]: <T>(x: T) => Maybe<T>;
[fl.map] = binder(this, this.map);
[fl.chain] = binder(this, this.flatMap);
[fl.ap]: <U>(m: Maybe<(x: T) => U>) => Maybe<U> = m => m.flatMap(f => this.map(f));
}
13 changes: 13 additions & 0 deletions tests/maybe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
});

0 comments on commit 8713a9d

Please sign in to comment.