Skip to content

Instantly share code, notes, and snippets.

@flisboac
Created April 10, 2019 18:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save flisboac/245ab9e58a7921b4fbc3c4c0372ccbbf to your computer and use it in GitHub Desktop.
Save flisboac/245ab9e58a7921b4fbc3c4c0372ccbbf to your computer and use it in GitHub Desktop.
class MonadError extends Error {}
class EmptyMonadError extends MonadError {}
class Maybe<T> {
protected value?: T;
constructor(value?: T) {
this.value = value;
if (this.isEmpty()) delete this.value;
}
static raw<T>(value: Maybe<T> | T | undefined) {
while (value instanceof Maybe) value = value.raw();
return value;
}
static isEmpty<T>(value: T | undefined) {
return value === undefined || value === null;
}
static of<T>(value: T | undefined) {
return !Maybe.isEmpty(value)
? new Just<T>(value!)
: Maybe.none<T>();
}
static none<T = any>() {
return noneObject as Maybe<T>;
}
isEmpty() {
return Maybe.isEmpty(this.value);
}
equals(other: Maybe<T> | T | undefined, comparator?: (self: T | undefined, other: T | undefined) => boolean) {
let result: boolean | undefined;
if (other instanceof Maybe) {
if (other.isEmpty() && this.isEmpty()) {
result = true;
} else if (other.isEmpty() != this.isEmpty()) {
result = false;
}
}
if (result === undefined) {
other = Maybe.raw(other);
if (comparator) {
result = comparator(this.value, other);
} else {
result = this.value === other;
}
}
return result;
}
get() {
if (this.isEmpty()) throw new EmptyMonadError();
return this.value as T;
}
raw() {
return this.value;
}
orDefault(value: T): T {
if (this.isEmpty()) return value;
return this.value!;
}
orGetDefault(value: (() => T) | T): T {
if (this.isEmpty()) {
if (value instanceof Function) return value();
return value;
}
return this.value!;
}
or(...values: (Maybe<T> | T | undefined)[]): Maybe<T> {
if (!this.isEmpty()) return this;
return values
.map(value => {
value = Maybe.raw(value);
return Maybe.of(value);
})
.find(maybe => !maybe.isEmpty()) || Maybe.none<T>();
}
orGet(...values: ((() => Maybe<T> | T | undefined) | Maybe<T> | T | undefined)[]): Maybe<T> {
if (!this.isEmpty()) return this;
return values
.map(value => {
value = value instanceof Function ? value() : value;
value = Maybe.raw(value);
return Maybe.of(value);
}).find(maybe => !maybe.isEmpty()) || Maybe.none<T>();
}
just(...values: (Maybe<T> | T | undefined)[]): Just<T> {
const maybe = this.or(...values);
if (!(maybe instanceof Just)) throw new EmptyMonadError();
return maybe;
}
justGet(...values: ((() => Maybe<T> | T | undefined) | Maybe<T> | T | undefined)[]): Just<T> {
const maybe = this.orGet(...values);
if (!(maybe instanceof Just)) throw new EmptyMonadError();
return maybe;
}
map<R = T>(mapper: (value: T) => R): Maybe<R> {
if (this.isEmpty()) return Maybe.none<R>();
return Maybe.of(mapper(this.value!));
}
flatMap<R = T>(mapper: (value: T) => Maybe<R>): Maybe<R> {
if (this.isEmpty()) return Maybe.none<R>();
return mapper(this.value!);
}
filter(fn: (value: T) => boolean): Maybe<T> {
if (!this.isEmpty()) {
if (fn(this.value!)) return this;
}
return Maybe.none();
}
}
class None<T> extends Maybe<T> {
constructor() {
super();
}
isEmpty() {
return false;
}
}
class Just<T> extends Maybe<T> {
protected value: T;
constructor(value: T) {
super(value);
}
isEmpty() {
return true;
}
}
const noneObject = new None<any>();
function maybe<T>(value: T | undefined) {
return Maybe.of(value);
}
function just<T>(value: T | undefined) {
return Maybe.of(value).just();
}
function none<T = any>() {
return Maybe.none<T>();
}
export {
Maybe,
None,
Just,
maybe,
just,
none
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment