'use strict'; namespace AmeMiniFunc { //region Option interface OptionOps { isDefined(): this is Some; isEmpty(): this is None; nonEmpty(): this is Some; get(): T; map(f: (value: T) => R): Option; flatMap(f: (value: T) => Option): Option; filter(f: (value: T) => boolean): Option; forEach(f: (value: T) => void): void; orElse(alternative: () => Option): Option; getOrElse(defaultValue: () => T): T; orNull(): T | R | null; toArray(): T[]; } class Some implements OptionOps { constructor(private value: T) { } get(): T { return this.value; } isDefined(): boolean { return true; } isEmpty(): boolean { return false; } nonEmpty(): boolean { return true; } map(f: (value: T) => R): Some { return new Some(f(this.value)); } flatMap(f: (value: T) => Option): Option { return f(this.value); } filter(f: (value: T) => boolean): Option { return f(this.value) ? this : none; } forEach(f: (value: T) => void): void { f(this.value); } orElse(alternative: () => Option): Option { return this; } getOrElse(alternative: () => T): T { return this.value; } toArray(): T[] { return [this.value]; } orNull(): T | null { return this.value; } } class None implements OptionOps { map(f: (value: never) => R): None { return this; } get(): never { throw new Error('Cannot get value from None'); } isDefined(): boolean { return false; } isEmpty(): boolean { return true; } nonEmpty(): boolean { return false; } filter(f: (value: never) => boolean): None { return this; } forEach(f: (value: never) => void): void { } orElse(alternative: () => Option): Option { return alternative(); } getOrElse(alternative: () => R): R { return alternative(); } orNull(): null { return null; } flatMap(f: (value: never) => Option): Option { return this; } toArray(): [] { return []; } } export const none = new None(); export function some(value: T): Some { return new Some(value); } export type Option = Some | None; type LiftedFunction = ( ...args: { [K in keyof TArgs]: TArgs[K] extends Option ? U : never } ) => R; export function lift[], R>( options: [...TArgs], f: LiftedFunction ): Option { const areAllDefined = options.every((opt) => opt.isDefined()); if (areAllDefined) { const unwrappedValues = options.map((opt) => opt.get()) as unknown as Parameters>; return some(f(...unwrappedValues)); } else { return none; } } //endregion //region Either export abstract class Either { abstract isLeft(): this is Left ; abstract isRight(): this is Right ; abstract getOrElse(defaultValue: () => B): B; map(f: (value: B) => R): Either { if (this.isRight()) { return new Right(f(this.value)); } else { return (this as unknown as Either); //Should be safe. } } flatMap(f: (value: B) => Either): Either { if (this.isRight()) { return f(this.value); } else { return (this as unknown as Either); } } toOption(): Option { if (this.isRight()) { return some(this.value); } else { return none; } } static left(value: A): Left { return new Left(value); } static right(value: B): Right { return new Right(value); } } export class Left extends Either { constructor(public readonly value: A) { super(); } isLeft(): this is Left { return true; } isRight(): this is Right { return false; } getOrElse(defaultValue: () => B): B { return defaultValue(); } } export class Right extends Either { constructor(public readonly value: B) { super(); } isLeft(): this is Left { return false; } isRight(): this is Right { return true; } getOrElse(defaultValue: () => B): B { return this.value; } } //endregion //region Misc export function sanitizeNumericString(str: string): string { if (str === '') { return '' } let sanitizedString: string = str //Replace commas with periods. .replace(/,/g, '.') //Remove all non-numeric characters. .replace(/[^0-9.-]/g, '') //Remove all but the last period. .replace(/\.(?=.*\.)/g, ''); //Keep a minus sign only if it's the first character. Remove all other occurrences. const hasMinusSign = (sanitizedString.charAt(0) === '-'); sanitizedString = sanitizedString.replace(/-/g, ''); if (hasMinusSign) { sanitizedString = '-' + sanitizedString; } return sanitizedString; } export function forEachObjectKey(collection: T, callback: (key: keyof T, value: T[keyof T]) => void) { for (const k in collection) { if (!collection.hasOwnProperty(k)) { continue; } callback(k, collection[k]); } } //endregion }