Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/maybe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export const binder = <T extends Function>(context: any, f: T): T => f.bind(cont

export type Nil = null | undefined;

export type NotVoid = object | string | number | boolean | undefined | null;

export interface MatchType<T, R> {
some?: (v: T) => R;
none?: () => R;
Expand All @@ -23,19 +25,19 @@ export default abstract class Maybe<T> {

abstract expect(msg?: string | Error): T;
abstract caseOf<R>(funcs: MatchType<T, R>): Maybe<R>;
abstract map<U>(f: (v: T) => Nullable<U>): Maybe<U>;
abstract map<U extends NotVoid>(f: (v: T) => Nullable<U>): Maybe<U>;
abstract tap(f: (v: T) => void): Maybe<T>;
abstract flatMap<U>(f: (v: T) => Maybe<U>): Maybe<U>;
abstract orElse<U>(def: U | (() => U)): T | U;
abstract or<U>(other: Maybe<U> | (() => Maybe<U>)): Maybe<T | U>;
abstract eq(other: Maybe<T>): boolean;
abstract asNullable(): T | null;

abstract join<U, R>(f: (x: T, y: U) => R | Nil, other: Maybe<U>): Maybe<R>;
abstract join<U, R extends NotVoid>(f: (x: T, y: U) => R | Nil, other: Maybe<U>): Maybe<R>;

// 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));
[fl.ap]: <U extends NotVoid>(m: Maybe<(x: T) => U>) => Maybe<U> = m => m.flatMap(f => this.map(f));
}
4 changes: 2 additions & 2 deletions src/some.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Nullable } from 'simplytyped';
import Maybe, { MatchType } from "./maybe";
import Maybe, { MatchType, NotVoid } from "./maybe";
import { maybe, none } from "./index";

export default class Some<T> extends Maybe<T> {
Expand Down Expand Up @@ -40,7 +40,7 @@ export default class Some<T> extends Maybe<T> {
.orElse(false);
}

join<U, R>(f: (x: T, y: U) => Nullable<R>, other: Maybe<U>): Maybe<R> {
join<U, R extends NotVoid>(f: (x: T, y: U) => Nullable<R>, other: Maybe<U>): Maybe<R> {
return this.flatMap(x => other.map(y => f(x, y)));
}

Expand Down
4 changes: 2 additions & 2 deletions src/transformer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ConstructorFor, Nullable } from 'simplytyped';
// @ts-ignore
import Maybe, { MatchType, Nil } from './maybe';
import Maybe, { MatchType, Nil, NotVoid } from './maybe';
import { maybe } from './index';

export interface Monad<T> {
Expand Down Expand Up @@ -29,7 +29,7 @@ export class MaybeT<T extends MonadLike<unknown>> {
return new MaybeT(monad);
}

map<U>(f: (v: MaybeValue<T>) => U): MaybeT<Monad<U>> {
map<U extends NotVoid>(f: (v: MaybeValue<T>) => U): MaybeT<Monad<U>> {
const map = getMap(this.value);
return new MaybeT(map(inner =>
maybe(inner)
Expand Down
16 changes: 9 additions & 7 deletions tests/maybe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Maybe, { maybe, none, some } from '../src';
// Helpers
// -------
const execEach = (...args: Array<() => any>) => () => args.forEach(arg => arg());
const noop = () => { /* stub */ };
const noop = () => null;
const raiseError = () => {
throw new Error('oops');
};
Expand Down Expand Up @@ -38,7 +38,8 @@ test('Calls map function when contained value is non-nil', () => {
const value = "i'm not nil";
const definitely = some(value);

definitely.map(v => expect(v).toBe(value));
// `void` to avoid passing void function to map
definitely.map(v => void expect(v).toBe(value));
});

test('Does not call map function when contained value is nil', () => {
Expand Down Expand Up @@ -272,7 +273,7 @@ test('join - calls f if both sides are some', () => {

const z = x.join((a, b) => a + b, y);

z.map(c => expect(c).toBe('hi there'));
z.tap(c => expect(c).toBe('hi there'));
});

test('join - does not call f if either side is none', () => {
Expand All @@ -286,9 +287,9 @@ test('join - does not call f if either side is none', () => {
const z2 = right.join((a, b) => a + b, left);
const z3 = right.join((a, b) => a + b, middle);

z1.map(fail).orElse(pass);
z2.map(fail).orElse(pass);
z3.map(fail).orElse(pass);
z1.tap(fail).orElse(pass);
z2.tap(fail).orElse(pass);
z3.tap(fail).orElse(pass);
});

// -------
Expand All @@ -301,5 +302,6 @@ test('fantasy-land/map - calls into the map method', () => {
const value = "i'm not nil";
const definitely = some(value);

definitely["fantasy-land/map"](v => expect(v).toBe(value));
// `void` to avoid passing void function to map
definitely["fantasy-land/map"](v => void expect(v).toBe(value));
});