// @flow /* eslint-disable no-use-before-define */ import type {Option} from './option.js'; import {some, none} from './option.js'; type EitherCommon<+A, +B> = { +failure: FailureProjection, +success: SuccessProjection, }; export type Failure<+A, +B> = EitherCommon & { isFailure: true, isSuccess: false, +failureValue: A, }; export type Success<+A, +B> = EitherCommon & { isFailure: false, isSuccess: true, +successValue: B, }; class AbstractEither { isFailure: $Subtype; isSuccess: $Subtype; failure: FailureProjection = (new FailureProjection((this: any))); success: SuccessProjection = (new SuccessProjection((this: any))); } class _FailureProjection { e: Either; map(f: A => X): Either { const {e} = this; return e.isFailure ? failure(f(e.failureValue)) : success(e.successValue); } flatMap(f: A => Either): Either { const {e} = this; return e.isFailure ? f(e.failureValue) : success(e.successValue); } getOrElse(def: X): (A | X) { const {e} = this; return e.isFailure ? e.failureValue : def; } filter(p: A => boolean): Option> { const {e} = this; if (e.isFailure && p(e.failureValue)) { return some(e); } else { return none; } } toOption(): Option { const {e} = this; return e.isFailure ? some(e.failureValue) : none; } } // $ExpectError class FailureProjection<+A, +B> extends _FailureProjection { +e: Either; // $ExpectError constructor(e: Either) { super(); // $ExpectError this.e = e; } get() { const {e} = this; if (e.isFailure) { return e.failureValue; } else { throw new Error('Either.failure.value on Success'); } } } class _SuccessProjection { e: Either; map(f: B => X): Either { const {e} = this; return e.isSuccess ? success(f(e.successValue)) : failure(e.failureValue); } flatMap(f: B => Either): Either { const {e} = this; return e.isSuccess ? f(e.successValue) : failure(e.failureValue); } getOrElse(def: X): (B | X) { const {e} = this; return e.isSuccess ? e.successValue : def; } filter(p: B => boolean): Option> { const {e} = this; if (e.isSuccess && p(e.successValue)) { return some(e); } else { return none; } } toOption(): Option { const {e} = this; return e.isSuccess ? some(e.successValue) : none; } } // $ExpectError class SuccessProjection<+A, +B> extends _SuccessProjection { +e: Either; // $ExpectError constructor(e: Either) { super(); // $ExpectError this.e = e; } get() { const {e} = this; if (e.isSuccess) { return e.successValue; } else { throw new Error('Either.success.value on Failure'); } } } class _Failure extends AbstractEither { failureValue: A; constructor(failureValue: A) { super(); this.failureValue = failureValue; } isFailure: true = true; isSuccess: false = false; } class _Success extends AbstractEither { successValue: B; constructor(successValue: B) { super(); this.successValue = successValue; } isFailure: false = false; isSuccess: true = true; } export const failure = (failureValue: A): Failure => new _Failure(failureValue); export const success = (successValue: B): Success => new _Success(successValue); export type Either<+A, +B> = Failure | Success; export default { failure, success, };