ts-union
Tiny library (<1Kb gzipped) for algebraic sum types that looks similar to swift enums. Inspired by unionize and F# discriminated-unions
Installation
npm add ts-union
NOTE: uses features from typescript 2.8
Usage
Create
import { Union, t } from 'ts-union';
const PaymentMethod = Union({
Cash: t(),
Check: t<CheckNumber>(),
CreditCard: t<CardType, CardNumber>()
});
type CheckNumber = number;
type CardType = 'MasterCard' | 'Visa';
type CardNumber = string;
Type constructors
const cash = PaymentMethod.Cash();
const check = PaymentMethod.Check(15566909);
const card = PaymentMethod.CreditCard('Visa', '1111-566-...');
const { Cash, Check, CreditCard } = PaymentMethod;
const anotherCheck = Check(566541123);
Matching
const str = PaymentMethod.match(cash, {
Cash: () => 'cash',
Check: n => `check num: ${n.toString()}`,
CreditCard: (type, n) => `${type} ${n}`
});
Also supports deferred (curried) matching and default case.
const toStr = PaymentMethod.match({
Cash: () => 'cash',
default: _v => 'not cash'
});
const str = toStr(card);
if (aka simplified match)
const str = PaymentMethod.if.Cash(cash, () => 'cash');
You can provide else case as well. In that case 'undefined' type will be removed.
const str = PaymentMethod.if.Check(
cash,
n => `check num: ${n.toString()}`,
_v => 'not check'
);
Type of resulted objects
At the moment types of cash, check, card are opaque.
type CashType = typeof cash;
The OpaqueUnion<...> type for PaymentMethod is accessible via T phantom property
type PaymentMethodType = typeof PaymentMethod.T;
Api and implementation details
If you will try to log the value for check you will see an array.
console.log(PaymentMethod.Check(15566909));
All values are arrays. The first element is the key to match against and the rest is payload. I decided not to expose that through typings but I might reconsider that in the future. Although you cannot use it for redux action you can safely use it for redux state.
Api
How to define shape
const U = Union({
Simple: t(),
One: t<string>(),
Const: t(3),
Two: t<string, number>(),
Three: t<string, number, boolean>()
});
Let's take a closer look at t
function
export declare type Types = {
(): NoData;
<T>(): One<T>;
<T>(val: T): Const<T>;
<T1, T2>(): Two<T1, T2>;
<T1, T2, T3>(): Three<T1, T2, T3>;
};
export declare const t: Types;
the actual implementation is pretty simple:
export const t: Types = ((val: any) => val) as any;
We just capture the constant and don't really care about the rest. Typescript will guide us to provide proper number of args for each case.
match accepts either a full set of props or a subset with default case.
export type MatchFunc<Record> = {
<Result>(cases: MatchCases<Record, Result>): (
val: OpaqueUnion<Record>
) => Result;
<Result>(val: OpaqueUnion<Record>, cases: MatchCases<Record, Result>): Result;
};
if either accepts a function that will be invoked (with a match) and/or else case.
{
<R>(val: OpaqueUnion<Rec>, f: (a: A) => R): R | undefined;
<R>(val: OpaqueUnion<Rec>, f: (a: A) => R, els: (v: OpaqueUnion<Rec>) => R): R;
}