import { Exception, Nullable, structuralHash, equals, compare } from "./Util.ts"; // Options are erased in runtime by Fable, but we have // the `Some` type below to wrap values that would evaluate // to `undefined` in runtime. These two rules must be followed: // 1- `None` is always `undefined` in runtime, a non-strict null check // (`x == null`) is enough to check the case of an option. // 2- To get the value of an option the `value` helper // below must **always** be used. // Note: We use non-strict null check for backwards compatibility with // code that use F# options to represent values that could be null in JS export type Option = T | Some | undefined; // Using a class here for better compatibility with TS files importing Some export class Some { public value: T; constructor(value: T) { this.value = value; } public toJSON() { return this.value; } // Don't add "Some" for consistency with erased options public toString() { return String(this.value); } public GetHashCode() { return structuralHash(this.value); } public Equals(other: Option): boolean { if (other == null) { return false; } else { return equals(this.value, other instanceof Some ? other.value : other); } } public CompareTo(other: Option) { if (other == null) { return 1; } else { return compare(this.value, other instanceof Some ? other.value : other); } } } export function nonNullValue(x: Nullable): T { if (x == null) { throw new Exception("Nullable has no value"); } else { return x; } } export function value(x: Option) { if (x == null) { throw new Exception("Option has no value"); } else { return x instanceof Some ? x.value : x; } } export function unwrap(opt: Option): T | undefined { return opt instanceof Some ? opt.value : opt; } export function some(x: T): Option { return x == null || x instanceof Some ? new Some(x) : x; } export function ofNullable(x: Nullable): Option { // This will fail with unit probably, an alternative would be: // return x === null ? undefined : (x === undefined ? new Some(x) : x); return x == null ? undefined : x; } export function toNullable(x: Option): Nullable { return x == null ? null : value(x); } export function flatten(x: Option>) { return x == null ? undefined : value(x); } export function toArray(opt: Option): T[] { return (opt == null) ? [] : [value(opt)]; } export function defaultArg(opt: Option, defaultValue: T): T { return (opt != null) ? value(opt) : defaultValue; } export function defaultArgWith(opt: Option, defThunk: () => T): T { return (opt != null) ? value(opt) : defThunk(); } export function orElse(opt: Option, ifNone: Option): Option { return opt == null ? ifNone : opt; } export function orElseWith(opt: Option, ifNoneThunk: () => Option): Option { return opt == null ? ifNoneThunk() : opt; } export function filter(predicate: (arg: T) => boolean, opt: Option): Option { return (opt != null) ? (predicate(value(opt)) ? opt : undefined) : opt; } export function map(mapping: (arg: T) => U, opt: Option): Option { return (opt != null) ? some(mapping(value(opt))) : undefined; } export function map2( mapping: (arg1: T1, arg2: T2) => Option, opt1: Option, opt2: Option): Option { return (opt1 != null && opt2 != null) ? mapping(value(opt1), value(opt2)) : undefined; } export function map3( mapping: (arg1: T1, arg2: T2, arg3: T3) => Option, opt1: Option, opt2: Option, opt3: Option): Option { return (opt1 != null && opt2 != null && opt3 != null) ? mapping(value(opt1), value(opt2), value(opt3)) : undefined; } export function bind(binder: (arg: T) => Option, opt: Option): Option { return opt != null ? binder(value(opt)) : undefined; } export function tryOp(op: (x: T) => U, arg: T): Option { try { return some(op(arg)); } catch { return undefined; } }