// @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,
};