ts-union
Library for algebraic sum types that looks similar to swift enums. Inspired by unionize and F# discriminated-unions
Installation
npm add ts-union
Usage
Create
const PaymentMethod = Union({
Cash: simple(),
Check: of<CheckNumber>(),
CreditCard: of2<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 (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()}`,
() => '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 debug 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: simple(),
One: of<string>(),
Const: ofConst(3),
Two: of2<string, number>(),
Three: of3<string, number, boolean>()
});
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: () => R): R;
}