free-types | Use Cases | Documentation | Guide | Algebraic data types
Creation | Helpers | Application | Decomposition | Composition | Adapters | Higher order types | Operators
In this documentation the types Mappable
and NonEmptyMappable
appear often. I use them to mean array and object literal types. The conflation of arrays and objects in one type is useful because no type definition can reject empty objects and thus expecting a NonEmptyMappable
will force you to check that the input is not empty regardless of it being an array or an object.
type | description |
---|---|
Extend interface $Foo extends Type {
type: 'foo'
} Access arguments with interface $Foo extends Type {
type: this[0]
} Define constraints on the inputs: // variadic types with no constraint
interface $Foo extends Type { ... }
interface $Foo extends Type<number> { ... }
interface $Foo extends Type<unknown[]> { ... }
// Setting the arity:
interface $Foo extends Type<1> { ... }
// Setting the arity + a specific constraint:
interface $Foo extends Type<[number, number]> { ... }
// optional parameters:
interface $Foo extends Type<[number, number?]> { ... }
// equivalent to the former:
interface $Foo extends Type {
constraints: [number, number?]
...
} Define constraints on the output: interface $Foo extends Type<unknown[], string> {
type: string
} Public fields:
| |
Turn a type type Foo = MapOver<[1, 2, 3], Const<'A'>>; // [A, A, A]
type Bar = MapOver<[1, 2, 3], $Next>; // [2, 3, 4] | |
A free version of type Consts = MapOver<[1,2,3], $Const>
// Consts : [Type<❋, 1>, Type<❋, 2>, Type<❋, 3>] | |
Create a new type $Foo = From<{ a: number, b: string, c: boolean }, ['b', 'a']>;
type Foo = apply<$Foo, ['a', 1]>
// Foo : { a: 1, b: "a", c: boolean }
type $Bar = From<[number, string, boolean], [1, 0]>;
type Bar = apply<$Bar, ['a', 1]>
// Bar : [1, "a", boolean] The values of type Rejected = apply<$Foo, [1, 'a']>
// --------
type Rejected = apply<$Bar, [1, 'a']>
// --------
// [1, "a"] doesn't satisfy the constraint [string, number] When type $Foo = From<{ a: number }>;
type Foo = apply<$Foo, [1]>
// Foo : { a: 1 }
type $Bar = From<[number, string]>;
type Bar = apply<$Bar, [1, 'foo']>
// Bar : [1, "foo"] The ordering of the arguments is only guarantied for tuples, so do supply | |
A variation of type $Foo = From<
{ a: { b: [number, string], c: boolean } },
[['a', 'b', 0], 'c']
>;
type Foo = apply<$Foo, [1, true]>
// Foo : { a: { b: [1, string], c: true } } I keep | |
A free version of type $Adders = MapOver<[1 ,2 ,3], $partial<$Add>>
// $Adders : [$Add<1>, $Add<2>, $Add<3>]
type Results = Ap<$Adders, [4, 5, 6]> // [5, 7, 9] | |
A free, flipped, version of type $Apply2 = $apply<[2]>;
type Result = apply<$Apply2, [$Next]> // 3 |
Helpers ↸
Helpers you may use to reduce boilerplate or assist with type checking.
type | description |
---|---|
Expect a free type with contravariant arguments. type Foo<$T extends Contra<$T, Type<2>>> =
apply<$T, ['foo', true]>
type Bar = Foo<$Add>
// ~~~~
| |
Check the indexation of interface $Foo extends Type<1> { type: At<1, this> }
// ----
// 'this' does not satisfy the constraint 'Type<2>'
Optionally take a fallback interface $UnaryFunction extends Type<2> {
type: (a: At<0, this, any>) => At<1, this>
}
type ReturnType = $UnaryFunction['type'] // (a: any) => unknown | |
Similar to interface $Foo extends Type<[string]> {
type: `${A<this>}`
} Is equivalent to: interface $Foo extends Type<[string]> {
type: `${this[0] & string}`
} When no argument is supplied, interface $Foo extends Type<[unknown, unknown[]]> {
type: [A<this>, ...Checked<B, this>]
} Is equivalent to: interface $Foo extends Type<[unknown, unknown[]]> {
type: [this[0], ...Checked<1, this>]
} | |
Similar to interface $Foo extends Type<[string]> {
type: `${Checked<0, this>}`
} Is equivalent to: interface $Foo extends Type<[string]> {
type: `${this[0] extends string ? this[0] : string}`
}
| |
Similar to interface $Foo extends Type<[string, string?]> {
type: `${A<this>}${Optional<1, this, ''>}`
} Is equivalent to: interface $Foo extends Type<[string, string?]> {
type: `${this[0] & string}${this[1] extends undefined ? '' : this[1]}`
}
| |
A more general version of interface $Foo extends Type<[string]> {
type: `${Lossy<0, this>}`
} Is equivalent to: interface $Foo extends Type<[string]> {
type: `${this[0] & string}`
} | |
Determine if
| |
A simple util which exposes | |
Alternate way to expect a free type in a type constraint by specifying the number interface $Foo extends Type<[number, string]> { ... }
type Foo<$T extends Remaining<$Foo, 1>> = apply<$T, ['foo']>
type $I = Type<[number, string]>
type Foo<$T extends Remaining<$I, 1>> = apply<$T, ['foo']> | |
Deeply widen a type type Foo = Widen<(a: 1) => Promise<{ a: 'bar', b: [true] }>>
// Foo : (a: number) => Promise<{ a: string, b: [boolean] }> Works out of the box on elements of type Wider = Widen<Foo<1>, [$Foo]> // Foo<number>
class Foo<T> { constructor (private value: T){} }
interface $Foo extends Type<1> { type: Foo<this[0]> }
type Foo<T> = { _tag: 'foo', value: T };
type Wider = Widen<Foo<5>> // { _tag: 'foo', value: number }; You can override this behaviour by providing your own pattern | |
Turn a tuple-like | |
Extract the range | |
Head<T> |
Extract the first element of the non-empty tuple T .
|
Tail<T> |
Extract all but the first element of the non-empty tuple T .
|
Last<T> |
Extract the last element of the tuple T or return [] if it is empty.
|
Init<T> |
Extract all but the last element of the non-empty tuple T .
|
Application ↸
type | description |
---|---|
Apply a free type interface $Map extends Type<2> { type: Map<A<this>, B<this>> };
type Foo = apply<$Map, [string, number]> // Map<string, number> | |
Apply a free type It is mostly equivalent to interface $Foo extends Type<[number]> {
type: this[0] extends number ? Foo<this[0]> : never
// try not to do this -----
}
type GenericFoo = Generic<$Foo> // Foo<number>
type ReturnType = $Foo['type']; // never I don't think it suites procedural types as much as it does "concrete" types, which is why it is used internally in | |
Return a new interface $Foo extends Type<3> {
type: Foo<A<this>, B<this>, C<this>>
}
type $Foo1 = partial<$Foo, [1]>
type Foo = apply<$Foo1, [2, 3]> // Foo<1, 2, 3> Optionally take a type Foo<$Q extends Type<[number, string]>> =
Bar<partial<$Q, [1], Type<[number, string]>>>
// ----------------------
type Bar<$Q extends Type<[string]>> = apply<$Q, ['bar']> | |
Behaves the same as type $Foo23 = partialRight<$Foo, [2, 3]>
type Foo = apply<$Foo23, [1]> // Foo<1, 2, 3> | |
An experimental variant of interface $Foo extends Type<3> {
type: Foo<A<this>, B<this>, C<this>>
}
type $Foo13 = _partial<$Foo, [1, _, 3]>
type Foo123 = apply<$Foo13, [2]> // Foo<1, 2, 3>
type Foo<A, B> = Bar<_partial<$Foo, [A, B]>>;
// "not a Type<1>" ~~~~~~~~~~~~~~~~~~~~~~
type Bar<$T extends Type<1>> = apply<$T, ['bar']>
// ------- | |
Partially apply This is useful when defining a flexible API: interface $Box3 extends Type<[number, number, number]> {
type: `${A<this>} x ${B<this>} x ${C<this>}`
}
type $Box<A extends number = never, B extends number = never> =
$Optional<$Box3, [A, B]> Free types defined this way can be partially applied with generics as well as with type Foo = apply<$Box<1>, [2, 3]>; // "1 x 2 x 3"
type Bar = apply<$Box<1, 2>, [3]>; // "1 x 2 x 3"
Fully applying | |
The same as type $Box<
A extends number | _ = never,
B extends number | _ = never,
C extends number | _ = never
> = _$Optional<$Box3, [A, B, C]>
type Foo = apply<$Box<_, 2>, [1, 3]> // "1 x 2 x 3" | |
The same as type Box<
A extends number = never,
B extends number = never,
C extends number = never
> = $Alter<$Box3, [A, B, C]>
type Foo = Box<1, 2, 3> // "1 x 2 x 3"
type Bar = apply<Box<1, 2>, [3]> // "1 x 2 x 3" Since free types are not first class, this behaviour may confuse users as it makes the |
Decomposition ↸
type | description |
---|---|
Decompose a type type Pair = unwrap<Set<number>>;
// Pair: Unwrapped<free.Set, [number]> Works out of the box on elements of type A = unwrap<Foo<number>, $Foo>;
// A: Unwrapped<$Foo, [number]> or an object/tuple/interface containing type B = unwrap<Foo<number>, [$Foo, $Bar]>;
// B: Unwrapped<$Foo, [number]>
| |
Deeply decompose type Tree = unwrapDeep< Map<1, Map<2, Map<3, 'value'>> >;
// Tree: Unwrapped<free.Map, [
// 1, Unwrapped<free.Map, [
// 2, Unwrapped<free.Map, [
// 3, "value"
// ]>
// ]>
// ]> | |
A free version of | |
A parameterised alias for the return value of
| |
A global extensible map from names to You can extend it with module augmentation: declare module 'free-types' {
interface TypesMap { Foo: $Foo }
}
class Foo<T> { constructor (private value: T) {} }
interface $Foo extends Type<1> { type: Foo<this[0]> } | |
A namespace containing a number of built-in type constructors:
type Foo<$T = free.Id> = {
foo: <A>(a: A) => apply<$T, [Bar<A>]>,
}
type SimpleFoo = Foo;
// { foo: <A>(a: A) => Bar<A> }
type FooAsync = Foo<free.Promise>
// { foo: <A>(a: A) => Promise<Bar<A>> }
type FooMap = Foo<partial<free.Map, [string]>>
// { foo: <A>(a: A) => Map<string, Bar<A>> }
type URLs = {
A: 'https://foo'
B: 'https://bar'
}
type Responses = {
A: { foo: number }
B: { bar: number }
}
type Queries = Lift<free.UnaryFunction, [URLs, Responses]>
// {
// A: (a: "https://foo") => { foo: number };
// B: (a: "https://bar") => { bar: number };
// }
type Commands = MapOver<URLs, partialRight<free.UnaryFunction, [void]>>
// {
// A: (a: "https://foo") => void;
// B: (a: "https://bar") => void;
// } |
Composition ↸
You should read the part of the guide dedicated to composition
type | description |
---|---|
Map over the individual arguments of type $AbsAdd = $Before<$Add, $Abs>;
type Foo = apply<$AbsAdd, [1, -2]> // same as apply<$Add, [1, 2]> Optionally take and index | |
Pipe the arguments list type Foo = Pipe<[1, 2], $Add, $Next, $Exclaim> // 4! When the types don't line up, the compiler tells you precisely what went wrong, using the inferred value as a hint: type Foo = Pipe<[1, 2], $Add, $Exclaim, $Next,>
// ~~~~~
// Type '$Next' does not satisfy the constraint 'Type<["3!"], number>'.
// Types of property 'constraints' are incompatible.
// Type '[number]' is not assignable to type '["3!"]'.
// Type 'number' is not assignable to type '"3!"'. When type Error<T extends number> = Pipe<[T], $Exclaim, $Next>;
// ~~~~~
// Type '$Next' does not satisfy the constraint
// '_<$Next, "should be", Type<[`${T & string}!` | `${T & number}!`], number>>' Be careful when using Pipe with a generic: Typescript will eagerly try to expand the type, which can work fine when nesting types for example, but can potentially fail if resolving the generic is required to work out the validity of the composition. Another possibility is that you end up with a huge thunk of nested conditional types waiting to receive a value. | |
Return the composition from left to right of the free types listed in type $AddNextExclaim = Flow<[$Add, $Next, $Exclaim]>
type Foo = apply<[1, 2], $AddNextExclaim> // 4! | |
Get the argument at index |
Adapters ↸
type | description |
---|---|
Turn a variadic | |
Turn a | |
Turn a type With = apply<$Rest<$Foo>, [1, 2, 3]>;
// is equivalent to
type Without = apply<$Foo, [[1, 2, 3]]>; The parameter | |
Turn a type With = apply<$Spread<$Foo>, [[1, 2, 3]]>;
// is equivalent to
type Without = apply<$Foo, [1, 2, 3]>; | |
A looser version of The length and ordering of the arguments are not type checked. Supernumerary arguments are ignored. If you supply an object type to | |
Turn a type With = apply<$Param<1, $Foo>, [1, 2, 3]>;
// is equivalent to
type Without = apply<$Foo, [2]>; | |
Narrow the constraints of the free type type $SplitDash = $Constrain<$Split<'-'>, [`${string}-${string}`]> If The return type advertised in the signature is updated by applying | |
Take a free type
Useful in situations where you created a free type programmatically and was thus not able to fine tune the advertised return type Following through with the previous example: // maybe consider writing a brand new free type though
type $SplitDash = $As<
$Constrain<$Split<'-'>, [`${string}-${string}`]>,
[string, string, ...string[]]
> * that is to say one must be a subtype or a supertype of the other | |
Not really an adapter as it does not convert a free type, but can be used as an alternative to type $Foo = Flow<[$Prop<'value'>, $Assert<number>, $Next]>
// ({ value : unknown } -> unknown), (unknown -> never), (number -> number)
|
Higher order types ↸
type | description |
---|---|
Create a new mappable of the same structure as type Foo = MapOver<[1, 2], $Next> // [2, 3]
type Bar = MapOver<(1 | 2)[], $Next> // (2 | 3)[]
type Baz = MapOver<{ a: 1, b: 2 }, $Next> // { a: 2, b: 3 }
| |
A free version of | |
Similar to Take a type FirstNames = ['Alan', 'Grace'];
type LastNames = ['Turing', 'Hopper'];
type Names = Lift<$Stitch<' '>, [FirstNames, LastNames]>;
// Names : ["Alan Turing", "Grace Hopper"] type FirstNames = { a: 'Alan', b: 'Grace' };
type LastNames = { a: 'Turing', b: 'Hopper' };
type Names = Lift<$Stitch<' '>, [FirstNames, LastNames]>;
// Names : { a: "Alan Turing", b: "Grace Hopper"} Supernumerary keys in any of the children are dropped. | |
A free versions of | |
Apply a mappable of free types type Foo = Ap<[$Next, $Prev], [2, 2]> // [3, 1]
type Bar = Ap<{a: $Next, b: $Prev}, {a: 2, b: 2}> // {a: 3, b: 1} When the keys mismatch, supernumerary elements in | |
Consume the mappable type Foo = Reduce<['a', 2, true], $Stitch<':'>>; // "a:2:true"
type Bar = Reduce<{ a: 1, b: 2, c: 3, d: 4 }, $Add>; // 10
| |
A free version of
| |
Identical to
| |
A free version of
| |
Match a type
type Number = Match<2, [
[number, 'I am a number'],
[string, 'I am a string'],
]>; // "I am a number" The placeholder type Nothing = Match<true, [
[number, 'I am a number'],
[string, 'I am a string'],
[otherwise, 'nothing matched']
]>; // "nothing matched" If type NotFound = Match<true, [
// ----
// Type 'true' does not satisfy the constraint
// '"T fell through without matching any case"
[number, 'I am a number'],
[string, 'I am a string'],
]>; // unique symbol Sadly, when type Switch<T extends number | string> = Match<T, [
[number, 'I am a number'],
[string, 'I am a string'],
], number | string>;
type Answer = Match<{}, [
[$IsEmpty, 'I am empty'],
[otherwise, 'I am not empty'],
]>; // "I am empty"
interface $IsEmpty extends Type<1> {
type: keyof this[0] extends never ? true : false
} If type Answer = Match<2, [
[number, $Next],
[otherwise, 'NaN'],
]>; // 3
The input to the callback is automatically destructured as described bellow. The elements of a tuple are supplied to type Answer = Match<['4', '2'],[
[[number, number], $Add],
[[string string], $Stitch<''>]
]>; // "42" The object values are supplied to type Three = Match<{ a: 1, b: 2 }, [
[{ a: number, b: number }, $Add],
[{ a: string, b: string }, Flow<[$Stitch<''>, $Length]>],
]>; // 3
Arbitrary types are destructured if type Args<T> = Match<T, [
[free.Map, free.Tuple],
[$Foo, free.Id],
]>;
class Foo<T> { constructor(private value: T) {}}
interface $Foo extends Type<1> { type: Foo<T> }
type MapArgs = Args<Map<string, number>> // [string, number]
type FooArgs = Args<Foo<string>> // string The tuple type Three = Match<{a: 'foo', b: 2}, [
[{a: string, b: number}, $Next, ['b']]
]>; // 3
The helper type Protected = Match<{ a: 1 }, [
[{ a: string }, free.Id],
[Protect<{ a: number }>, free.Id]
[{ a: boolean }, free.Id]
]> // { a: 1 } | |
A free version of
| |
Filter the mappable Filter<[1, 'a', 2, true], number> // [1, 2]
Filter<{ a: 1, b: 'a', c: 2, d: true }, number> // { a: 1, c: 2 }
Filter<[1, 'a', 2, true], $IsNumber> // [1, 2]
Filter<{ a: 1, b: 'a', c: 2, d: true }, $IsNumber> // { a: 1, c: 2 }
interface $IsNumber extends Type<1> {
type: this[0] extends number ? true : false
} | |
A free version of | |
The exact opposite of Filter. FilterOut<[1, 'a', 2, true], number> // ['a', true]
FilterOut<{ a: 1, b: 'a', c: 2, d: true }, number> // { b: 'a', d: true } When using a predicate, it is equivalent to calling FilterOut<[1, 'a', 2, true], $IsNumber> // ['a', true]
FilterOut<{ a: 1, b: 'a', c: 2, d: true }, $IsNumber> // { b: 'a', d: true } | |
A free version of | |
Partition the tuple type NumericList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
type Numbers = NumericList[number];
interface $Sizes extends Type<[Numbers], string> {
type: this[0] extends 0 ? 'null'
: this[0] extends 1|2|3 ? 'small'
: this[0] extends 4|5|6 ? 'medium'
: 'big'
}
type GroupedBySize = GroupBy<NumericList, $Sizes>;
// {
// null: [0],
// small: [1, 2, 3],
// medium: [4, 5, 6],
// big: [7, 8, 9]
// } | |
A free version of | |
Partition the union type Numbers = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
interface $Sizes extends Type<[Numbers], string> {
type: this[0] extends 0 ? 'null'
: this[0] extends 1|2|3 ? 'small'
: this[0] extends 4|5|6 ? 'medium'
: 'big'
}
type GroupedBySize = GroupByUnion<Numbers, $Sizes>;
// {
// null: 0,
// small: 1 | 2 | 3,
// medium: 4 | 5 | 6,
// big: 7 | 8 | 9
// } Optionally take a transfrom type GroupedBySize = GroupBy<Numbers, $Sizes, $Addlen>;
// {
// null: 1,
// small: 4 | 5 | 6,
// medium: 7 | 8 | 9,
// big: 10 | 11 | 12
// }
interface $AddLen extends $Transform {
type: Add<this[0], this[1]>
} | |
A free version of |
Operators and other utils ↸
Every n-ary type listed bellow can be partially applied with generics. For convenience, this type of application behaves like PartialRight
on non-commutative binary types such as $Exclude
$Subtract
or $Concat
.
type | description |
---|---|
$Intersect |
A free version of A & B with a slight modification in that the intersection is simplified to A or B if one is a subtype of the other.
|
$Unionize |
A free version of A | B
|
$Exclude |
A free version of Exclude<A, B>
|
false | never | any
are considered falsy values
type | description |
---|---|
$Extends |
A free version of A extends B , returns boolean |
$Includes |
A free version of B extends A , returns boolean
code> |
$RelatesTo |
A free version of A extends B OR B extends A , returns boolean
|
$Eq |
A free version of A extends B AND B extends A , returns boolean
|
$Not<$T> |
An adapter inverting a Type<1, boolean>
|
$And |
A free version of A && B
|
$Or |
A free version of A || B
|
Take a predicate By default the constraints on the new | |
Take a predicate |
type | description |
---|---|
$Head |
A free version of T[0]
|
$Tail |
A free version of [unknown, ...infer T]
|
$Last |
A free version of T[T['length'] - 1]
|
$Init |
A free version of [...infer T, unknown]
|
$Concat |
A free version of [...A, ...B] , [...A, B] , [A, ...B] , [A, B]
|
By Showable
I mean string | number | bigint | boolean | null | undefined
type | description |
---|---|
Show<T, Depth?> |
Recursively convert an arbitrary type to a Depends on |
$Show<Depth?> |
A free version of Show
|
$Length |
Get the length of a string
|
$ParseInt |
convert a Limited to the range Since TS 4.8 you can implement an unbounded version with: T extends `${infer N extends number}` ? N : number |
$Stitch |
A free version of ${B}${A}${C} where A , B and C are Showable
|
$Join<S> |
Intersperse a tuple of Showable with the supplied separator and merge the result into one string. If the tuple is empty, $Join returns ""
|
$Split |
Split the string B into a tuple of substrings with the separator A
|
$StrReplace |
Recursively replace A with B in C
|
type | description |
---|---|
Index a type Foo = apply<$Prop<'a'>, [{ a: 1, b: 2 }]>; // 1
type Foo = apply<$Prop, ['a', { a: 1, b: 2 }]>; // 1 Partially applying // $GetA : { [k: string | number | symbol]: unknown } -> unknown
type $GetA = partial<$Prop, ['a']>;
// $GetA : { a: unknown } -> unknown
type $GetA = $Prop<'a'>;
// $GetA : { a: number } -> number
type $GetA = $Prop<'a', number>; Because | |
Set the property matching the key type Foo = apply<$SetProp<42>, ['a', {a: 1}]>; // { a: 42 }
type Foo = apply<$SetProp<42, 'a'>, [{a: 1}]>; // { a: 42 }
type Foo = apply<$SetProp, [42, 'a', {a: 1}]>; // { a: 42 } If
| |
A stricter version of | |
Similar to type Foo = apply<$SetIndex, [42, 0, [1]]>; // [42]
type Foo = apply<$SetIndex<42>, [0, [1]]>; // [42]
type Foo = apply<$SetIndex<42, 0>, [[1]]>; // [42] |
type | description |
---|---|
$Next |
A free version of T + 1 , with T in the range [0, 63] .
|
$Prev |
A free version of T - 1 , with T in the range [1, 64] .
|
$Abs |
A free version of |T| with T in the range [-64, Infinity[ .
|
$Add * |
A free version of A + B
|
$Multiply * |
A free version of A * B
|
$Subtract * |
A free version of A - B
|
$Divide * |
A free version of A / B (Euclidean)
|
$Lt * |
A free version ofA < B
|
$Lte * |
A free version ofA <= B
|
$Gt * |
A free version ofA > B
|
$Gte * |
A free version ofA >= B
|
$Max * |
A free version of Max<A, B>
|
$Min * |
A free version of Min<A, B>
|
* the inputs and output of these types must be in the range [0, 64]