函数式(functional)、可谱写(composable)的辅助类(type utilities)
此库旨在提供适用于不同领域的辅助类(utility types),这些类可以通过高阶类(higher-kinded types)以函数的形式加以映射和组合。
利用此库提供的可谱写(composable)且可高效编译(compile-time efficient)的辅助类,您可以更好的编写可靠且类安全的程序。
我们意在实现对数百种高阶类种(kind categories)的支持,例如 List, Boolean, String, Function 等等。除此之外,我们还内置了帮助类谱写(composing type)的类组合接口(combinators)。
> npm install hkt-toolbelt
简而言之,hkt-toolbelt
可以将以下代码:
/**
* 去掉元组中的非数元素
*/
type FilterNum<T extends unknown[]> = T extends [Head, ...Tail]
? Head extends number
? [Head, ...FilterNum<Tail>]
: FilterNum<Tail>
: [];
简化成这样:
import { $, List, Conditional } from "hkt-toolbelt";
type FilterNum = List.Filter<Conditional.Extends<number>>;
您可以通过谱写 hkt-toolbelt
的高阶原始类让自己编写的复杂类更可读。
您可以通过 $
运算符来使用您的高阶类:
type Result = $<FilterNum, [1, "x", 2, "y", 3]>; // [1, 2, 3]
导入也可以以子路径的形式实现。
import { $ } from `hkt-toolbelt`;
import { Filter } from `hkt-toolbelt/list`;
import { Extends } from `hkt-toolbelt/conditional`;
> HKT 是 higher-kinded type 简称
Typescript 有两种 不同 的类型结构: “类”和“泛型”。
- type(类): 编译时用来描述值的表达式。
- generic(泛型): 类似模板的形式参数类,可以通过提供一个或多个实际参数实例化,并解析出类(type)。
泛型在 Typescript 中并非一类对象(first-class citizen)——除非已经提供全部所需的实际参数类,否则它们不可以被直接调用;泛型不能作为参数提供给其他泛型,也不能被返还——这些都是由语言本身的局限导致。
hkt-toolbelt
额外引入两个类型结构:
- kind(高阶类): 编译时用来描述类(type)的表达式,实际设计成函数的形式,可以接纳类作为参数进行运算。
- generic kind(高阶泛型): 能够返还高阶类的泛型。
而以类(type)为参数的高阶类(kind)运算,我们通过泛型$<kind, type>
来实现。
利用高阶类,我们可以写出仅凭泛型无法实现的新类型,譬如对泛用函数的紧缩谱写(narrow composition)。
即便某个类可以通过泛型表达,我们也可以利用高阶类实现更美观、易用的接口。
关于术语的使用 高阶类的英文,严格来说仅使用 kind 并不正确,不过“higher-kinded type”有点长,所以用 'kind' 简略表达.
有时候我们也会用“hk-type”来表达,这样其实更合适。
中文译者注 原文中出现的 higher-kinded type(高阶类), type function(类函数), higher-kinded-type function(高阶类函数)等词汇,虽然严格来说存在差别,但多数时候可以理解为相似的概念。
我们准备了可以帮助您上手 hkt-toolbelt
的资料,其中含有对相关概念和用法的详解。
- 此库灵感来源于 ts-toolbelt
- 超棒的 TS 学习资源: type-challenges
- 用于值(value)的辅助函数: lodash
- API
此库的高阶类多为柯里化(currying)函数,旨在支持以无参风格(point-free style)实现类谱写。因此,使用类接口(API types)时需要先提供“操作”(operations),再提供操作的目标数据。
同样为了支持柯里化和无参风,所有类函数(type functions),例如 full Kinds,一次只接受 一个 参数。
$
操作符用于将类作为参数提供给高阶类函数(higher-kinded-type function),相当于 TypeScript 中的 F<A>
。
import { $, String } from `hkt-toolbelt`;
type Result = $<String.Append<` world`>, `hello`>; // `hello world`
$$
操作符用于将类作为起始参数提供给一条高阶类通道(pipeline of kinds),它其实是简化同时使用 $
和 Kind.Pipe
的语法糖,用来免去完整调用后者的麻烦。
Kind.Pipe
以从左到右的顺序组合类函数。
@参考 $
@参考 Kind.Compose
import { $$, Kind, String } from `hkt-toolbelt`;
type Result = $$<[String.Append<" world">, String.Append<"!">], "hello">; // "hello world!"
Cast
用于将一个类转换成另一个类,相当于 TypeScript 中的 A as B
,多用于微调。
import { Cast } from `hkt-toolbelt`;
type Result = Cast<`hello`, string>; // `hello`
And
纳入一个布尔类并返还一个函数,这个函数可以纳入另一个布尔类并返回两个布尔类 &&
的结果。
import { $, Boolean } from `hkt-toolbelt`;
type Result = $<Boolean.And<true>, false>; // false
Or
纳入一个布尔类并返还一个函数,这个函数可以纳入另一个布尔类并返回两个布尔类 ||
的结果。
import { $, Boolean } from `hkt-toolbelt`;
type Result = $<Boolean.Or<true>, false>; // true
Not
纳入一个布尔类并返还相反的布尔类。
import { $, Boolean } from `hkt-toolbelt`;
type Result = $<Boolean.Not, true>; // false
Self
返还自身,因而可以利用$无限调用。
import { $, Combinator } from `hkt-toolbelt`;
type Result = $<$<Combinator.Self, `foo`>, `foo`>; // Combinator.Self
ApplySelf
纳入一个高阶类,并将其作为参数提供给其自身,可用于创建 syntho 递归高阶类。
import { $, Combinator } from `hkt-toolbelt`;
type Result = $<Combinator.ApplySelf, Function.Identity>; // Function.Identity
Equals
用于检验一个类是否等于另一个类,等同于 TypeScript 中的 A extends B ? ( B extends A ? true : false ) : false
。
Equals
返还一个高阶类函数(higher-kinded-type function),此函数可以纳入另一个类,然后返还一个布尔类。
import { $, Conditional } from `hkt-toolbelt`;
type Result = $<$<Conditional.Equals, `foo`>, `bar`>; // false
Extends
用于检验一个类是否是另一个类的子类,等同于 TypeScript 中的 A extends B ? true : false
。
需先提供母类,再提供子类。
Extends
返还一个高阶类函数(higher-kinded-type function),此函数可以纳入另一个类,然后返还一个布尔类。
import { $, Conditional } from `hkt-toolbelt`;
type Result = $<$<Conditional.Extends, string>, `bar`>; // true
If
根据条件返还一个类,等同于 TypeScript 中的 P<X> extends true ? T<X> : E<X>
,不过其以无参风格(point-free style)纳入参数 X
。
If
纳入一个条件函数(predicate),一个真时类,和一个假时类;其返还一个高阶类函数,此函数可纳入一个类并根据判定结果返还对应真/假时类。
import { $, Conditional } from "hkt-toolbelt";
type Result = $<
Conditional.If<
Conditional.Equals<"foo">,
String.Append<"bar">,
String.Append<"baz">
>,
"foo"
>; // "foobar"
此高阶类专用于类层面的流程控制。
Function
是所有函数的母类,也即所有函数都是 Function
的子类。其并非高阶类且不能直接调用。
Constant
纳入一个类并返还一个函数,该函数可纳入任何类并返还最初纳入的类,即它会忽略后来纳入的类且始终返还最初的类。
import { $, Function } from `hkt-toolbelt`;
type Result = $<$<Function.Constant, `foo`>, number>; // `foo`
Identity
纳入一个类,并在高阶类的层面上返还该类。
import { $, Function } from `hkt-toolbelt`;
type Result = $<Function.Identity, `foo`>; // `foo`
Kind
代表了可以通过 $
将类作为参数进行运算的类函数。
您可以选择给 Kind 提供一个函数类(function type)以提高其内部参数和返还值的优先级,以次创造新的高阶类。
Composable
检验一个高阶类元组中的高阶类是否可以组合(composable)。如果元组中高阶类
import { $, Kind, String } from `hkt-toolbelt`;
type Result = $<Kind.Composable, [String.Append<`bar`>, String.Append<`foo`>]>; // true
Compose
可以将一个由高阶类组成的元组组合成一个类函数(type function)。
Compose
会检查提供的高阶类元组是否可以组合(composable),且会返回一个高阶类函数(higher-kinded-type function),此函数可纳入一个类然后返还组合的结果。
Compose
从右向左执行函数,即——遵循数学的传统——元组中最后一个函数最先执行。
import { $, Kind, String } from `hkt-toolbelt`;
type Result = $<Kind.Compose<[String.Append<`bar`>, String.Append<`foo`>]>, ``>; // `foobar`
Pipe
纳入一个由类函数(type function)组成的元组,并将它们“输送”到另一个类函数(type function)中。这里的执行顺序是从左向右,即元组中的首个函数最先执行,与 Compose
刚好相反。
Pipe
对程序员来说更直观,因为其阅读顺序与执行顺序统一。$$
语法糖实际运行的就是 Pipe
。
import { $, Kind, String } from `hkt-toolbelt`;
type Result = $<Kind.Pipe<[String.Append<`foo`>, String.Append<`bar`>]>, ``>; // `foobar`
_
是类函数(type function)被调用前临时“占位”的独有类。Kind._
由 $
调用。
Map
传入一个 函数类
,并返回一个接受元组类型的高级类型。它将给定的类型函数应用于元组中的每个元素。
import { $, List, String } from `hkt-toolbelt`;
type Result = $<List.Map<String.Append<`bar`>>, [`foo`, `baz`]>; // [`foobar`, `bazbar`]
Find
函数先接受一个 函数类
,然后接受一个 元组
,并返回 finder 函数返回 true
的第一个元组元素。如果不存在这样的元素, Find
返回 never
。
import { $, List, String } from `hkt-toolbelt`;
type Result = $<List.Find<String.StartsWith<`foo`>>, [`bar`, `foobar`]>; // `foobar`
Filter
传入一个类型函数和一个元组,并按输入元组的顺序返回一个元组,因此只有 Filter 函数返回 true
的元素保留在结果元组中。
import { $, List, String } from `hkt-toolbelt`;
type Result = $<List.Filter<String.StartsWith<`foo`>>, [`bar`, `foobar`]>; // [`foobar`]
Append
传入一个类型和一个元组,并应用该类型,使其被附加到所提供的元组的末尾。
import { $, List } from `hkt-toolbelt`;
type Result = $<List.Append<`bar`>, [`foo`, `baz`]>; // [`foo`, `baz`, `bar`]
First
传入一个元组,并返回该元组的第一个元素。
import { $, List } from `hkt-toolbelt`;
type Result = $<List.First, [`foo`, `bar`]>; // `foo`
Last
传入一个 元组
,并返回元组的最后一个元素。在具有可变元素的元组的情况下,可变元素被正确处理,即使它是中缀。
import { $, List } from `hkt-toolbelt`;
type Result = $<List.Last, [`foo`, `bar`, `baz`]>; // `baz`
Pair
传入一个 元组
,并返回元组的元组,其中每个元组是原始元组的一对元素,按顺序排列。如。'[1, 2, 3] '变成'[[1, 2],[2, 3]]'。
import { $, List } from `hkt-toolbelt`;
type Result = $<List.Pair, [1, 2, 3]>; // [[1, 2], [2, 3]]
对于可变元组,通过引入联合来表示可变对元素的可能组合来处理可变元素。
Every
接受一个操作函数类和一个元组,如果元组中的每个元素都满足这个操作函数,则返回 true
,否则返回 false
。
import { $, List, Conditional } from `hkt-toolbelt`;
type Result = $<List.Every<Conditional.Extends<number>>, [1, 2, 3]>; // true
Some
接受一个操作函数类和一个元组,如果元组中至少有一个元素满足操作函数类,则返回' true ',否则返回
false` 。
import { $, List, Conditional } from `hkt-toolbelt`;
type Result = $<List.Some<Conditional.Extends<string>>, [1, 2, 3]>; // false
Reverse 函数接受一个元组,并返回一个包含倒序元素的元组。
这种类型正确地处理可变元组类型,例如。[1、2、……string[]] "变成"[…string[] 2 1]”。
import { $, List } from `hkt-toolbelt`;
type Result = $<List.Reverse, [1, 2, 3]>; // [3, 2, 1]
IsVariadic
传入一个元组,如果元组是可变的,则返回 true
,否则返回 false
。
如果一个元组的长度不确定,我们就认为它是可变的。
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"
StartsWith
函数接受一个模版字符串,并返回它是否以给定的前缀开头,根据情况返回 true
或 false
。
当 开始于 这个开头 string
,因此 StartsWith<string>
对于所有后续的字符串类型将返回 true。
然而, string
开头没有特定的前缀,因此 $<startwith <
f >, string>
将导致 false。所有字符串也都以空字符串开头。
import { $, String } from `hkt-toolbelt`;
type Result = $<String.StartsWith<`foo`>, `foobar`>; // true
EndsWith
传入一个模版字符串,并返回是否以给定后缀结束,根据情况返回 true
或 false
。
import { $, String } from `hkt-toolbelt`;
type Result = $<String.EndsWith<`bar`>, `foobar`>; // true
Includes
传入一个字符串文字并返回它是否包含给定字符串的子字符串,根据情况返回 true
或 false
。
@see String.StartsWith
import { $, String } from `hkt-toolbelt`;
type Result = $<String.Includes<`foo`>, `barfoobar`>; // true
Append
传入一个模版字符串并返回一个高阶函数类,该函数接受一个字符串并返回将传入的模版字符串添加到字符串末尾的结果。
import { $, String } from `hkt-toolbelt`;
type Result = $<String.Append<`bar`>, `foo`>; // `foobar`
Prepend
传入一个模版字符类型并返回一个高阶类型函数,该函数接受一个字符串并返回将传入的模版字符串添加到字符串开头的结果。
import { $, String } from `hkt-toolbelt`;
type Result = $<String.Prepend<`foo`>, `bar`>; // `foobar`
IsTemplate
传入一个字符串并返回它是否是模板字符串类型,根据情况返回 true
或 false
。
如果一个字符串不能被简化为字面值字符串,即如果其中包含 ${string}
,则该字符串被认为是模板字面值。
这可能会是一步非常耗费性能的计算。
import { $, String } from `hkt-toolbelt`;
type Result = $<String.IsTemplate, `foo${string}`>; // true
Join
传入一个模版字符串,并返回一个更高类型的函数,该函数接受一个字符串元组,并返回以模版字符串值作为分隔符连接元组中的字符串的结果。
import { $, String } from `hkt-toolbelt`;
type Result = $<String.Join<` `>, [`foo`, `bar`, `baz`]>; // `foo bar baz`
Join
也可以处理模板字符串,并将正确处理模板字面量的嵌入表达式。在可变元组输入的情况下,我们将联接解析为 string
。分隔符和元组元素都支持字符串联合。
Split
传入一个模版字符串值并返回一个更高类型的函数,该函数接受一个字符串并返回一个字符串元组,其中原始字符串根据字符串字面值进行拆分。
import { $, String } from `hkt-toolbelt`;
type Result = $<String.Split<` `>, `foo bar baz`>; // [`foo`, `bar`, `baz`]
Split
也可以处理模板字符串,并将正确处理模板字面量的嵌入表达式。但是,所有字符串字面值分隔符的结果都是 string[]
作为分割结果。分隔符和元组元素都支持字符串联合。
"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" }]