UNPKG

@practical-fp/union-types

Version:

A Typescript library for creating discriminating union types.

258 lines 6.89 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); function tag(tag, value) { return { tag: tag, value: value, }; } exports.tag = tag; /** * Extracts the value form a @link Variant} instance. * @param variant */ function untag(variant) { return variant.value; } exports.untag = untag; /** * Type guard for narrowing down the type of a {@link Variant}. * @param variant * @param tag * @example * type Union = * | Variant<"1", number> * | Variant<"2", string> * * function doSomething(union: Union) { * // union.value has type number | string * * if (hasTag(union, "1")) { * // union.value has type number now * } * } */ function hasTag(variant, tag) { return variant.tag === tag; } exports.hasTag = hasTag; /** * Factory function for creating a type guard which narrows down the type of a {@link Variant}. * @param tag * @example * type Union = * | Variant<"1", number> * | Variant<"2", string> * * function doSomething(list: Union[]) { * // filtered has type Variant<"1", number>[] * const filtered = list.filter(predicate("1")) * } */ function predicate(tag) { return function (variant) { return hasTag(variant, tag); }; } exports.predicate = predicate; /** * Symbol for declaring a wildcard case in a {@link match} expression. */ exports.WILDCARD = Symbol("Match Wildcard"); /** * Function for matching on the tag of a {@link Variant}. * All possible cases need to be covered, unless a wildcard case is present. * @param variant * @param cases * @example * type Union = * | Variant<"Num", number> * | Variant<"Str", string> * | Variant<"Bool", boolean> * * function doSomething(union: Union) { * return match(union, { * Num: number => number * number, * Str: string => `Hello, ${string}!`, * Bool: boolean => !boolean, * }) * } * * function doSomethingElse(union: Union) { * return match(union, { * Str: string => `Hello, ${string}!`, * [WILDCARD]: () => "Hello there!", * }) * } * @deprecated Use {@link matchExhaustive} or {@link matchWildcard} instead. */ function match(variant, cases) { var tag = variant.tag; if (typeof cases[tag] === "function") { return cases[tag](variant.value); } else if (typeof cases[exports.WILDCARD] === "function") { return cases[exports.WILDCARD](); } throw new Error("No case matched tag " + tag + "."); } exports.match = match; /** * Function for matching on the tag of a {@link Variant}. * All possible cases need to be covered. * @param variant * @param cases * @example * type Union = * | Variant<"Num", number> * | Variant<"Str", string> * | Variant<"Bool", boolean> * * function doSomething(union: Union) { * return matchExhaustive(union, { * Num: number => number * number, * Str: string => `Hello, ${string}!`, * Bool: boolean => !boolean, * }) * } */ function matchExhaustive(variant, cases) { return match(variant, cases); } exports.matchExhaustive = matchExhaustive; /** * Function for matching on the tag of a {@link Variant}. * Not all cases need to be covered, a wildcard case needs to be present. * @param variant * @param cases * @example * type Union = * | Variant<"Num", number> * | Variant<"Str", string> * | Variant<"Bool", boolean> * * function doSomething(union: Union) { * return matchWildcard(union, { * Str: string => `Hello, ${string}!`, * [WILDCARD]: () => "Hello there!", * }) * } */ function matchWildcard(variant, cases) { return match(variant, cases); } exports.matchWildcard = matchWildcard; /** * Utility function for asserting that all cases have been covered. * @param variant * @example * type Union = * | Variant<"1", string> * | Variant<"2", number> * * function doSomething(union: Union) { * switch(union.tag) { * case "1": * alert(union.value) * break * case "2": * alert(union.value.toFixed(0)) * break * default: * // compile error if we've forgotten a case * assertNever(union) * } * } */ function assertNever(variant) { throw new Error("Unreachable state reached!"); } exports.assertNever = assertNever; /** * Function for creating a constructor for the given variant. * * In case the variant type uses unconstrained generics, * pass unknown as its type arguments. * * In case the variant type uses constrained generics, * pass the constraint type as its type arguments. * * Use {@link impl} instead if your environment has support for {@link Proxy}. * * @example * type Result<T, E> = * | Variant<"Ok", T> * | Variant<"Err", E> * * const Ok = constructor<Result<unknown, unknown>, "Ok">("Ok") * const Err = constructor<Result<unknown, unknown>, "Err">("Err") * * let result: Result<number, string> * result = Ok(42) * result = Err("Something went wrong") * * Ok.is(result) // false * Err.is(result) // true * * Ok.tag // "Ok" * Err.tag // "Err" */ function constructor(tagName) { function constructor(value) { return tag(tagName, value); } constructor.tag = tagName; constructor.is = predicate(tagName); return constructor; } exports.constructor = constructor; /** * Same as {@link constructor}, but does not support generics. * @param tagName */ function strictConstructor(tagName) { return constructor(tagName); } exports.strictConstructor = strictConstructor; /** * Function for generating an implementation for the given variants. * * In case the variant type uses unconstrained generics, * pass unknown as its type arguments. * * In case the variant type uses constrained generics, * pass the constraint type as its type arguments. * * @example * type Result<T, E> = * | Variant<"Ok", T> * | Variant<"Err", E> * * const {Ok, Err} = impl<Result<unknown, unknown>>() * * let result: Result<number, string> * result = Ok(42) * result = Err("Something went wrong") * * Ok.is(result) // false * Err.is(result) // true * * Ok.tag // "Ok" * Err.tag // "Err" */ function impl() { return new Proxy({}, { get: function (_, tagName) { return constructor(tagName); }, }); } exports.impl = impl; /** * Same as {@link impl}, but does not support generics. */ function strictImpl() { return impl(); } exports.strictImpl = strictImpl; //# sourceMappingURL=index.js.map