@typed-f/either
Version:
[![NPM Version][either-npm-version-badge]][either-npm] [repo-circleci-badge]: https://img.shields.io/circleci/project/github/Ailrun/typed-f/master.svg?logo=circleci [![Known Vulnerabilities][either-snyk-badge]][either-snyk] [![Supported TypeScript Version
255 lines (221 loc) • 6.48 kB
text/typescript
/*
* Copyright 2018-present Junyoung Clare Jang
*/
import { Fun } from '@typed-f/function';
import { MatchPatterns, Matchable } from '@typed-f/matchable';
import { Monad2 } from '@typed-f/monad';
import { Setoid2 } from '@typed-f/setoid';
/**
* Import to declare module
*/
import { } from '@typed-f/tagged';
type EitherTag = '__typed_f__Either__';
const EitherTag: EitherTag = '__typed_f__Either__';
declare module '@typed-f/tagged' {
export interface Tag2List<L, R> {
[EitherTag]: Either<L, R>;
}
}
export interface EitherPatterns<L, R, U> extends MatchPatterns<U> {
left(l: L): U;
right(r: R): U;
}
//tslint:disable-next-line: max-line-length
abstract class BaseEither<L, R> implements Matchable, Setoid2<EitherTag>, Monad2<EitherTag, L, R> {
//tslint:disable-next-line: variable-name
public __typed_f__tag__: EitherTag = EitherTag;
protected abstract _kind: Either.Kind;
public abstract isLeft(): this is Left<L, R>;
public abstract isRight(): this is Right<L, R>;
public abstract leftOr(def: L): L;
public abstract leftOrThrow(err: Error): L;
public abstract leftOrCompute(f: Fun<[R], L>): L;
public abstract rightOr(def: R): R;
public abstract rightOrThrow(err: Error): R;
public abstract rightOrCompute(f: Fun<[L], R>): R;
public abstract matchWith<U>(cases: EitherPatterns<L, R, U>): U;
public abstract equals(other: Either<any, any>): boolean;
public abstract map<U>(f: Fun<[R], U>): Either<L, U>;
public abstract ap<U>(f: Either<L, Fun<[R], U>>): Either<L, U>;
public abstract bind<U>(f: Fun<[R], Either<L, U>>): Either<L, U>;
public caseOf = this.matchWith;
public notEquals(other: Either<any, any>): boolean {
return !this.equals(other);
}
public lift = this.map;
public fmap = this.map;
public unit<U>(v: U): Either<any, U> {
return new Right(v);
}
public of = this.unit;
public chain = this.bind;
}
export class Left<L, R> extends BaseEither<L, R> {
private readonly _value: L;
protected _kind: Either.Kind.Left = Either.Kind.Left;
public get value(): L {
return this._value;
}
public constructor(value: L) {
super();
this._value = value;
}
public isLeft(): this is Left<L, R> {
return true;
}
public isRight(): this is Right<L, R> {
return false;
}
public leftOr(_def: L): L {
return this._value;
}
public leftOrThrow(_err: Error): L {
return this._value;
}
public leftOrCompute(_f: Fun<[R], L>): L {
return this._value;
}
public rightOr(def: R): R {
return def;
}
public rightOrThrow(err: Error): R {
throw err;
}
public rightOrCompute(f: Fun<[L], R>): R {
return f(this._value);
}
public matchWith<U>(cases: EitherPatterns<L, R, U>): U {
return cases.left(this._value);
}
public equals(other: Either<any, any>): boolean {
if (!(other instanceof BaseEither) ||
other.isRight()) {
return false;
}
if (typeof this._value === 'object' &&
this._value != undefined &&
typeof (this._value as any).equals === 'function') {
return Boolean((this._value as any).equals(other._value));
}
return this._value === other._value;
}
public map<U>(_f: Fun<[R], U>): Either<L, U> {
return this as Left<L, any>;
}
public ap<U>(_f: Either<L, Fun<[R], U>>): Either<L, U> {
return this as Left<L, any>;
}
public bind<U>(_f: Fun<[R], Either<L, U>>): Either<L, U> {
return this as Left<L, any>;
}
}
export class Right<L, R> extends BaseEither<L, R> {
private readonly _value: R;
protected _kind: Either.Kind.Right = Either.Kind.Right;
public get value(): R {
return this._value;
}
public constructor(value: R) {
super();
this._value = value;
}
public isLeft(): this is Left<L, R> {
return false;
}
public isRight(): this is Right<L, R> {
return true;
}
public leftOr(def: L): L {
return def;
}
public leftOrThrow(err: Error): L {
throw err;
}
public leftOrCompute(f: Fun<[R], L>): L {
return f(this._value);
}
public rightOr(_def: R): R {
return this._value;
}
public rightOrThrow(_err: Error): R {
return this._value;
}
public rightOrCompute(_f: Fun<[L], R>): R {
return this._value;
}
public matchWith<U>(cases: EitherPatterns<L, R, U>): U {
return cases.right(this._value);
}
public equals(other: Either<any, any>): boolean {
if (!(other instanceof BaseEither) ||
other.isLeft()) {
return false;
}
if (typeof this._value === 'object' &&
this._value != undefined &&
typeof (this._value as any).equals === 'function') {
return (this._value as any).equals(other._value);
}
return this._value === other._value;
}
public map<U>(f: Fun<[R], U>): Either<L, U> {
return new Right(f(this._value));
}
public ap<U>(f: Either<L, Fun<[R], U>>): Either<L, U> {
if (f.isLeft()) {
return f as Either<L, any>;
}
return this.map(f._value);
}
public bind<U>(f: Fun<[R], Either<L, U>>): Either<L, U> {
return f(this._value);
}
}
export type Either<L, R> =
| Left<L, R>
| Right<L, R>
;
export namespace Either {
export enum Kind {
Left,
Right,
}
export function unit<R>(value: R): Either<any, R> {
return new Right(value);
}
export const of = unit;
export function sequenceObject<L, O extends object>(
obj: { [K in keyof O]: Either<L, O[K]> },
): Either<L, O> {
const entries = (Object.keys(obj) as (keyof O)[])
.map((key): [keyof O, Either<L, O[keyof O]>] => [key, obj[key]]);
const leftEntries = entries
.filter((entry): entry is [keyof O, Left<L, any>] => entry[1].isLeft());
if (leftEntries.length > 0) {
return new Left(leftEntries[0][1].value);
}
const result = entries
.filter((entry): entry is [keyof O, Right<L, O[keyof O]>] => {
return !entry[1].isLeft();
})
.reduce<{}>((acc, [key, { value }]) => {
return {
...acc,
[key]: value,
};
}, {}) as O;
return new Right(result);
}
export function sequenceArray<L, R>(array: Either<L, R>[]): Either<L, R[]> {
return array.reduce((acc: Either<L, R[]>, mayv) => {
return acc.ap(mayv.map((v) => (arr: R[]) => [...arr, v]));
}, new Right([]));
}
export function map<L, R, U>(
f: Fun<[R], U>,
): Fun<[Either<L, R>], Either<L, U>> {
return function (eitherv) {
return eitherv.map(f);
};
}
}