@practical-fp/union-types
Version:
A Typescript library for creating discriminating union types.
258 lines • 6.89 kB
JavaScript
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
;