Functional and composable type utilities
This library provides type-level utilities across many domains that may be mapped and combined in a functional way, using higher-kinded types.
Write robust, type-safe software with the benefit of composable and compile-time efficient types.
We aim to support hundreds of kind categories, including List, Boolean, String, Function, and more. We also provide a set of combinators for composing types.
> npm install hkt-toolbelt
In short, hkt-toolbelt
let's you go from this:
/**
* Remove all non-numeric elements from a tuple.
*/
type FilterNum<T extends unknown[]> = T extends [infer Head, ...infer Tail]
? Head extends number
? [Head, ...FilterNum<Tail>]
: FilterNum<Tail>
: [];
To this:
import { $, List, Conditional } from "hkt-toolbelt";
type FilterNum = $<List.Filter, $<Conditional.Extends, number>>;
hkt-toolbelt
let's you express advanced types in a readable way, via composition of higher-kinded primitives.
You apply your kinds to an input with the $
operator:
type Result = $<FilterNum, [1, "x", 2, "y", 3]>; // [1, 2, 3]
You can also optionally import subpaths.
import { $ } from "hkt-toolbelt";
import { Filter } from "hkt-toolbelt/list";
import { Extends } from "hkt-toolbelt/conditional";
type FilterNum = $<Filter, $<Extends, number>>;
> HKT stands for "higher-kinded type"
Typescript has two different constructions: types, and generics.
- type: An compile-time expression that is used to describe a value.
- generic: A 'template' type that can be instantiated with one or more type arguments, and resolves to a type.
Generics are not first-class citizens in Typescript - you cannot reference them without immediately supplying all of their type arguments. You can't pass in generics as arguments to other generics, or return them. This is a limitation of the language.
hkt-toolbelt
introduces two additional constructions:
- kind: A compile-time expression that is used to describe a type, and is parameterized such that it may be applied to an argument type.
- generic kind: A generic type that returns a kind.
We apply kinds to types using the $<kind, type>
generic.
Using kinds allows us to represent new types that are not possible with generics alone. For example: the narrow composition of generic functions.
As well, for even types that are representible using generics, we can use kinds to provide a more ergonomic API and elegant implementation.
Note on Terminology Technically, using the word kind like this is incorrect. However, always mentioning 'higher-kinded type' is cumbersome, so we use 'kind' as shorthand.
In some places we use 'hk-type' instead, which is more correct.
We have additional resources to help you get started with hkt-toolbelt
, that go in depth on the concepts and usage.
- [Custom Kinds] - How do I create my own higher kinded types?
- [Kind Constraints] - How do I constrain a hk-type's input?
- [HK-Type Encoding] - Details on the internal encoding.
- Inspired by ts-toolbelt
- Awesome TS learning resource: type-challenges
- Value-level utilities: lodash
Note: Many examples are out of date, as many higher-kinded types have been modified to be more partially applicable.
Generally, types such as
List.Filter<Fn>
are now$<List.Filter, Fn>
.
- API
The curried nature of the functions in this library is intended to be utilized to compose types using point-free style. In this respect, API types will first take in 'operations' and then the data to be operated on.
All higher-order types take in one argument, to support currying and point-free style. The $
operator applies a parameter value to a higher-order type.
The $
operator is used to apply a higher-kinded-type function to a type. It is equivalent to the F<A>
syntax in TypeScript.
import { $, String } from "hkt-toolbelt";
type Result = $<String.Append<" world">, "hello">; // "hello world"
The $$
operator is used to apply a pipeline of kinds to a designated input type. This is a syntactic sugar for the $
and Kind.Pipe
operators, to avoid the need for explicitly calling Kind.Pipe
.
Kind.Pipe
composes kinds from left-to-right.
@see $
@see Kind.Pipe
import { $$, Kind, String } from "hkt-toolbelt";
type Result = $$<[String.Append<" world">, String.Append<"!">], "hello">; // "hello world!"
The Cast
type is used to cast a type to another type. It is equivalent to the A as B
syntax in TypeScript. For subtle cases.
import { Cast } from "hkt-toolbelt";
type Result = Cast<"hello", string>; // "hello"
The And
type takes in a boolean and returns a function that takes in another boolean and returns the result of the two booleans being &&
'd together.
import { $, Boolean } from "hkt-toolbelt";
type Result = $<Boolean.And<true>, false>; // false
The Or
type takes in a boolean and returns a function that takes in another boolean and returns the result of the two booleans being ||
'd together.
import { $, Boolean } from "hkt-toolbelt";
type Result = $<Boolean.Or<true>, false>; // true
The Not
type takes in a boolean and returns the opposite boolean.
import { $, Boolean } from "hkt-toolbelt";
type Result = $<Boolean.Not, true>; // false
The Self
kind returns itself. This means it can be applied with $ infinitely.
import { $, Combinator } from "hkt-toolbelt";
type Result = $<$<Combinator.Self, "foo">, "foo">; // Combinator.Self
The ApplySelf
kind takes in a kind, and applies that kind to itself. This can be used to model recursion for higher-order types.
import { $, Combinator } from "hkt-toolbelt";
type Result = $<Combinator.ApplySelf, Function.Identity>; // Function.Identity
The Equals
type is used to check if a type is equal to another type. It is equivalent to the A extends B ? (B extends A ? true : false) : false
syntax in TypeScript.
Equals
returns a higher-kinded-type function that takes a type and returns a boolean.
import { $, Conditional } from "hkt-toolbelt";
type Result = $<$<Conditional.Equals, "foo">, "bar">; // false
The Extends
type is used to check if a type is a subtype of another type. It is equivalent to the A extends B ? true : false
syntax in TypeScript.
The first type passed in is the supertype, and the second type passed in is the subtype.
Extends
returns a higher-kinded-type function that takes a type and returns a boolean.
import { $, Conditional } from "hkt-toolbelt";
type Result = $<$<Conditional.Extends, string>, "bar">; // true
The If
type is used to conditionally return a type. It is equivalent to the P<X> extends true ? T<X> : E<X>
syntax in TypeScript, but can be supplied with its argument X
in a point-free style.
If
takes in a predicate, a true type, and a false type. It returns a higher-kinded-type function that takes in a type and returns the result of the associated branch.
import { $, Conditional } from "hkt-toolbelt";
type Result = $<
Conditional.If<
Conditional.Equals<"foo">,
String.Append<"bar">,
String.Append<"baz">
>,
"foo"
>; // "foobar"
This is a hk-type used to designate type-level control flow.
The Function
type is a supertype of all functions, i.e. all functions are a subtype of Function
. It is not a kind and cannot be applied.
The Constant
type takes in a type and returns a function that takes in any type and returns the original type. It ignores its applied input and always returns the configured type.
import { $, Function } from "hkt-toolbelt";
type Result = $<$<Function.Constant, "foo">, number>; // "foo"
The Identity
type takes in a type and returns the same type, on the higher-kinded-type level.
import { $, Function } from "hkt-toolbelt";
type Result = $<Function.Identity, "foo">; // "foo"
The Kind
type denotes a type function that may be applied to a type using $
.
The Kind type can optionally be provided a function type to increase the specificity of its internal parameter and return types. This is used to create new kinds.
The Composable
type checks whether a tuple of kinds are composable. A tuple of kinds is composable if the output of kind
import { $, Kind, String } from "hkt-toolbelt";
type Result = $<Kind.Composable, [String.Append<"bar">, String.Append<"foo">]>; // true
The Compose
type takes in a tuple of type functions, and composes them into one type function.
Compose
checks that the tuple of kinds is composable, and returns a higher-kinded-type function that takes in a type and returns the result of the composition.
Compose
executes functions from right to left, i.e. the last function in the tuple is executed first - as is traditional in mathematics.
import { $, Kind, String } from "hkt-toolbelt";
type Result = $<Kind.Compose<[String.Append<"bar">, String.Append<"foo">]>, "">; // "foobar"
The Pipe
type takes in a tuple of type functions, and pipes them into one type function. This operates from left to right, i.e. the first function in the tuple is executed first. This is the opposite order of Compose
.
Pipe
is often more intuitive for programmers since it reads in order of execution. This is what $$
uses internally.
import { $, Kind, String } from "hkt-toolbelt";
type Result = $<Kind.Pipe<[String.Append<"foo">, String.Append<"bar">]>, "">; // "foobar"
The _
type represents the 'unique placeholder type' used in type functions before application. Kind._
is used by $
for application.
The Map
function takes in a type function, and returns a higher kinded type that takes in a tuple type. It applies the given type function over every element in the tuple.
import { $, List, String } from "hkt-toolbelt";
type Result = $<List.Map<String.Append<"bar">>, ["foo", "baz"]>; // ["foobar", "bazbar"]
The Find
function takes in a type function, then a tuple, and returns the first tuple element for which the finder function returns true
. If no such element exists, Find
returns never
.
import { $, List, String } from "hkt-toolbelt";
type Result = $<List.Find<String.StartsWith<"foo">>, ["bar", "foobar"]>; // "foobar"
The Filter
function takes in a type function, and a tuple, and returns a tuple in-order of the input tuple, whereby only elements for which the filter function returns true
remain in the resultant tuple.
import { $, List, String } from "hkt-toolbelt";
type Result = $<List.Filter<String.StartsWith<"foo">>, ["bar", "foobar"]>; // ["foobar"]
The Append
function takes in a type, and a tuple, and applies the type such that it is appended to the end of the provided tuple.
import { $, List } from "hkt-toolbelt";
type Result = $<List.Append<"bar">, ["foo", "baz"]>; // ["foo", "baz", "bar"]
The First
function takes in a tuple, and returns the first element of the tuple.
import { $, List } from "hkt-toolbelt";
type Result = $<List.First, ["foo", "bar"]>; // "foo"
The Last
function takes in a tuple, and returns the last element of the tuple. In the case of tuples with variadic elements, the variadic element is properly handled, even if it's infix.
import { $, List } from "hkt-toolbelt";
type Result = $<List.Last, ["foo", "bar", "baz"]>; // "baz"
The Pair
function takes in a tuple, and returns a tuple of tuples, where each tuple is a pair of the original tuple's elements, in order. e.g. [1, 2, 3]
becomes [[1, 2], [2, 3]]
.
import { $, List } from "hkt-toolbelt";
type Result = $<List.Pair, [1, 2, 3]>; // [[1, 2], [2, 3]]
For variadic tuples, the variadic element is handled via introducing unions to represent the possible combinations of variadic pair elements.
The Every
function takes in a predicate function, and a tuple, and returns true
if every element in the tuple satisfies the predicate function, and false
otherwise.
import { $, List, Conditional } from "hkt-toolbelt";
type Result = $<List.Every<Conditional.Extends<number>>, [1, 2, 3]>; // true
The Some
function takes in a predicate function, and a tuple, and returns true
if at least one element in the tuple satisfies the predicate function, and false
otherwise.
import { $, List, Conditional } from "hkt-toolbelt";
type Result = $<List.Some<Conditional.Extends<string>>, [1, 2, 3]>; // false
The Reverse
function takes in a tuple, and returns a tuple with the elements in reverse order.
This kind properly handles variadic tuple types, e.g. [1, 2, ...string[]]
becomes [...string[], 2, 1]
.
import { $, List } from "hkt-toolbelt";
type Result = $<List.Reverse, [1, 2, 3]>; // [3, 2, 1]
The IsVariadic
type takes in a tuple, and returns true
if the tuple is variadic, and false
otherwise.
We consider a tuple to be variadic if it has an indeterminate length.
import { List } from "hkt-toolbelt";
type Result = List.IsVariadic<[1, 2, 3]>; // false
The Keys
function takes in an object type, and returns a tuple of the keys of the object.
import { $, Object } from "hkt-toolbelt";
type Result = $<Object.Keys, { foo: string; bar: number }>; // ["foo", "bar"]
The Values
function takes in an object type, and returns a tuple of the values of the object.
import { $, Object } from "hkt-toolbelt";
type Result = $<Object.Values, { foo: string; bar: number }>; // [string, number]
The MapKeys
function takes in a type function, and an object type, and returns an object type with the keys of the original object type mapped by the given type function.
import { $, Object, String } from "hkt-toolbelt";
type Result = $<Object.MapKeys<String.Append<"bar">>, { foo: string }>; // { foobar: string }
The MapValues
function takes in a type function, and an object type, and returns an object type with the values of the original object type mapped by the given type function.
import { $, Object, String } from "hkt-toolbelt";
type Result = $<Object.MapValues<String.Append<"bar">>, { foo: "foo" }>; // { foo: "foobar" }
The DeepMap
function takes in a type function, and an object type, and returns an object type where every value in the object is mapped by the given type function.
import { $, Object, String } from "hkt-toolbelt";
type Result = $<
Object.DeepMap<String.Append<"bar">>,
{ name: { first: "foo"; last: "bar" } }
>; // { name: { first: "foobar"; last: "barbar" } }
The Paths
type takes in an object type, and returns a tuple of tuples, where each tuple is a path to a value in the object.
import { $, Object } from "hkt-toolbelt";
type Result = $<Object.Paths, { name: { first: "foo"; last: "bar" } }>; // [["name", "first"], ["name", "last"]]
The At
function takes in a key, and an object type, and returns the value at the given key in the object.
import { $, Object } from "hkt-toolbelt";
type Result = $<Object.At<"name">, { name: "foo" }>; // "foo"
The AtPath
function takes in a path, and an object type, and returns the value at the given path in the object.
import { $, Object } from "hkt-toolbelt";
type Result = $<
Object.AtPath<["name", "first"]>,
{ name: { first: "foo"; last: "bar" } }
>; // "foo"
The StartsWith
function takes in a string literal and returns whether or not it starts with the given prefix, returning true
or false
as appropriate.
All strings start with string
, so StartsWith<string>
will return true for all subsequent string types.
However, string
starts with no particular prefix, so $<StartsWith<"f">, string>
will result in false. All strings start with the empty string, as well.
import { $, String } from "hkt-toolbelt";
type Result = $<String.StartsWith<"foo">, "foobar">; // true
The EndsWith
function takes in a string literal and returns whether or not it ends with the given suffix, returning true
or false
as appropriate.
@see String.StartsWith
import { $, String } from "hkt-toolbelt";
type Result = $<String.EndsWith<"bar">, "foobar">; // true
The Includes
function takes in a string literal and returns whether or not it is a substring of the given string, returning true
or false
as appropriate.
@see String.StartsWith
import { $, String } from "hkt-toolbelt";
type Result = $<String.Includes<"foo">, "barfoobar">; // true
The Append
function takes in a string literal and returns a higher-kinded-type function that takes in a string and returns the result of appending the string literal to the end of the string.
import { $, String } from "hkt-toolbelt";
type Result = $<String.Append<"bar">, "foo">; // "foobar"
The Prepend
function takes in a string literal and returns a higher-kinded-type function that takes in a string and returns the result of prepending the string literal to the beginning of the string.
import { $, String } from "hkt-toolbelt";
type Result = $<String.Prepend<"foo">, "bar">; // "foobar"
The IsTemplate
function takes in a string and returns whether or not it is a template literal, returning true
or false
as appropriate.
A string is considered to be a template literal if it cannot be reduced to a literal string, i.e. if it contains ${string}
within it.
This is a potentially expensive type operation.
import { $, String } from "hkt-toolbelt";
type Result = $<String.IsTemplate, `foo${string}`>; // true
The Join
function takes in a string literal and returns a higher-kinded-type function that takes in a tuple of strings and returns the result of joining the strings in the tuple with the string literal acting as the separator.
import { $, String } from "hkt-toolbelt";
type Result = $<String.Join<" ">, ["foo", "bar", "baz"]>; // "foo bar baz"
Join
can handle template literal strings as well, and will properly handle the template literal's embedded expressions. In the case of variadic tuple input, we resolve the join to string
. String unions are supported for both the separator and the tuple elements.
The Split
function takes in a string literal and returns a higher-kinded-type function that takes in a string and returns a tuple of strings, where the original string is split on the string literal.
import { $, String } from "hkt-toolbelt";
type Result = $<String.Split<" ">, "foo bar baz">; // ["foo", "bar", "baz"]
Split
can handle template literal strings as well, and will properly handle the template literal's embedded expressions. However, all string literal delimiters result in string[]
as the split result. String unions are supported for both the separator and the tuple elements.
"If ya ain't
[First]
, you're[Last]
" - Ricky Bobby
The First
function takes in a string and returns the first character of the string.
import { $, String } from "hkt-toolbelt";
type Result = $<String.First, "foo">; // "f"
The Last
function takes in a string and returns the last character of the string.
import { $, String } from "hkt-toolbelt";
type Result = $<String.Last, "foo">; // "o"
The Tail
function takes in a string and returns the string with the first character removed.
import { $, String } from "hkt-toolbelt";
type Result = $<String.Tail, "foobar">; // "oobar"
The Init
function takes in a string and returns the string with the last character removed.
import { $, String } from "hkt-toolbelt";
type Result = $<String.Init, "foobar">; // "fooba"
The Replace
generic, given two 'From' and 'To' types that represent a string to replace, and a string to replace it with, returns a higher-kinded-type that takes in a string and returns the result of replacing all instances of the 'From' string with the 'To' string.
import { $, String } from "hkt-toolbelt";
type Result = $<String.Replace<"foo", "bar">, "foo foo foo">; // "bar bar bar"
The Reverse
function takes in a string and returns the string with the characters in reverse order.
import { $, String } from "hkt-toolbelt";
type Result = $<String.Reverse, "foobar">; // "raboof"
The IsString
function takes in a type and returns whether or not it is a string, returning true
or false
as appropriate.
import { $, String } from "hkt-toolbelt";
type Result = $<String.IsString, "foobar">; // true
The ToUpper
function takes in a string and returns the string with all characters converted to uppercase.
import { $, String } from "hkt-toolbelt";
type Result = $<String.ToUpper, "foobar">; // "FOOBAR"
The ToLower
function takes in a string and returns the string with all characters converted to lowercase.
import { $, String } from "hkt-toolbelt";
type Result = $<String.ToLower, "FOOBAR">; // "foobar"
The Display
function takes in a type and attempts to force the Typescript compiler to display the resolved type in IDEs and other tools.
This is a useful internal tool to ensure resultant types remain legible.
import { $, Type } from "hkt-toolbelt";
type Result = $<Type.Display, "foobar">; // "foobar"
The ValueOf
function takes in a type and returns the associated union value of the type, a higher-kinded equivalent to the T[keyof T]
operator.
import { $, Type } from "hkt-toolbelt";
type Result = $<Type.ValueOf, { foo: "bar" }>; // "bar"
The ToIntersection
function takes in a union type and returns the intersection of all the types in the union.
import { $, Union } from "hkt-toolbelt";
type Result = $<Union.ToIntersection, { foo: "bar" } | { bar: "bar" }>; // { foo: "bar"; bar: "bar" }
The ToTuple
function takes in a union type and returns a tuple of all the types in the union.
import { $, Union } from "hkt-toolbelt";
type Result = $<Union.ToTuple, { foo: "bar" } | { bar: "bar" }>; // [{ foo: "bar" }, { bar: "bar" }]