[英]Typescript subtract two numbers at compile time
我需要一个实用程序类型Subtract<A, B>
其中A
和B
是数字。 例如:
type Subtract<A extends number, B extends number> = /* implementation */
const one: Subtract<2, 1> = 1
const two: Subtract<4, 2> = 2
const error: Subtract<2, 1> = 123 // Error here: 123 is not assignable to type '1'.
Subtract<A, B>
的参数总是数字文字或编译时间常数。 我不需要
let foo: Subtract<number, number> // 'foo' may have 'number' type.
好吧,我认为上面的文字可能是XY问题,所以我想解释为什么我需要减法。 我有一个多维数组,具有Dims
维度。 调用slice
方法时,其尺寸会减小。
interface Tensor<Dimns extends number> {
// Here `I` type is a type of indeces
// and its 'length' is how many dimensions
// are subtracted from source array.
slice<I extends Array<[number, number]>>(...indeces: I): Tensor<Dimns - I['length']>
// here I need to subtract ^
}
例子:
declare const arr: Tensor<4, number>
arr.slice([0, 1]) // has to be 3d array
arr.slice([0, 1], [0, 2]) // has to be 2d array
arr.slice([0, 1]).slice([0, 2]) // has to be 2d array
您可以看到Dims
泛型如何依赖于传递给slice()
的参数数量。
如果很难使Subtract<A, B>
类型,是否可以减少类型? 所以,我可以做到以下几点:
interface Tensor<Dimns extends number> {
// Here `Decrement<A>` reduces the number of dimensions by 1.
slice(start: number, end: number): Tensor<Decrement<Dimns>>
}
TypeScript不支持编译时算术。 但是,可以使用数组强制执行某些类似的操作,但您必须定义自己的方法算法。 我会在前面警告你,这绝对是可怕的。
首先为数组操作定义一些基本类型:
type Tail<T> = T extends Array<any> ? ((...x: T) => void) extends ((h: any, ...t: infer I) => void) ? I : [] : unknown;
type Cons<A, T> = T extends Array<any> ? ((a: A, ...t: T) => void) extends ((...i: infer I) => void) ? I : unknown : never;
这些给你一些数组类型的力量,例如Tail<['foo', 'bar']>
给你['bar']
和Cons<'foo', ['bar']>
给你['foo', 'bar']
。
现在,您可以使用基于数组的数字(而非number
)定义一些算术概念:
type Zero = [];
type Inc<T> = Cons<void, T>;
type Dec<T> = Tail<T>;
因此,数字1将在此系统中表示为[void]
,2为[void, void]
,依此类推。 我们可以将加法和减法定义为:
type Add<A, B> = { 0: A, 1: Add<Inc<A>, Dec<B>> }[Zero extends B ? 0 : 1];
type Sub<A, B> = { 0: A, 1: Sub<Dec<A>, Dec<B>> }[Zero extends B ? 0 : 1];
如果您已确定,还可以以类似的方式定义乘法和除法运算符。 但就目前而言,这足以作为一个基本的算术系统使用。 例如:
type One = Inc<Zero>; // [void]
type Two = Inc<One>; // [void, void]
type Three = Add<One, Two>; // [void, void, void]
type Four = Sub<Add<Three, Three>, Two>; // [void, void, void, void]
定义一些其他实用程序方法来从number
常量来回转换。
type N<A extends number, T = Zero> = { 0: T, 1: N<A, Inc<T>> }[V<T> extends A ? 0 : 1];
type V<T> = T extends { length: number } ? T['length'] : unknown;
现在你可以像这样使用它们了
const one: V<Sub<N<2>, N<1>>> = 1;
const two: V<Sub<N<4>, N<2>>> = 2;
const error: V<Sub<N<2>, N<1>>> = 123; // Type '123' is not assignable to type '1'.
所有这一切都是为了展示TypeScript的类型系统有多强大,以及你可以推动它做多少事情而不是真正的设计。 它似乎只能可靠地工作到N<23>
左右(可能是由于TypeScript中的递归类型的限制)。 但是你真的应该在生产系统中这样做吗?
当然,这种类型的虐待是一种有趣的(至少对我来说),但它太复杂, 太容易使简单的错误,是非常困难的调试。 我强烈建议您只需对常量类型进行硬编码( const one: 1
),或者如评论所示,重新考虑您的设计。
对于更新的问题,如果Tensor
类型可以像上面Tail
所做的那样容易地减少(考虑到它是一个接口,这是值得怀疑的),你可以这样做:
type Reduced<T extends Tensor<number>> = T extends Tensor<infer N> ? /* construct Tensor<N-1> from Tensor<N> */ : Tensor<number>;
interface Tensor<Dimns extends number> {
slice(start: number, end: number): Reduced<Tensor<Dimns>>;
}
但是,由于张量往往只有几个维度,我认为只需要编写一些用户最有可能需要担心的情况就足够了:
type SliceIndeces<N extends number> = number[] & { length: N };
interface Tensor<Dims extends number> {
slice(this: Tensor<5>, ...indeces: SliceIndeces<1>): Tensor<4>;
slice(this: Tensor<5>, ...indeces: SliceIndeces<2>): Tensor<3>;
slice(this: Tensor<5>, ...indeces: SliceIndeces<3>): Tensor<2>;
slice(this: Tensor<5>, ...indeces: SliceIndeces<2>): Tensor<1>;
slice(this: Tensor<4>, ...indeces: SliceIndeces<1>): Tensor<3>;
slice(this: Tensor<4>, ...indeces: SliceIndeces<2>): Tensor<2>;
slice(this: Tensor<4>, ...indeces: SliceIndeces<3>): Tensor<1>;
slice(this: Tensor<3>, ...indeces: SliceIndeces<1>): Tensor<2>;
slice(this: Tensor<3>, ...indeces: SliceIndeces<2>): Tensor<1>;
slice(this: Tensor<2>, ...indeces: SliceIndeces<1>): Tensor<1>;
slice(...indeces:number[]): Tensor<number>;
}
const t5: Tensor<5> = ...
const t3 = t5.slice(0, 5); // inferred type is Tensor<3>
我知道这会导致一些漂亮的“ WET ”代码,但维护此代码的成本仍然可能低于维护自定义算术系统的成本,如上所述。
请注意,官方TypeScript声明文件通常使用类似这样的模式(请参阅lib.esnext.array.d.ts
)。 只有最常见的用例才包含强类型定义。 对于任何其他用例,期望用户在适当的地方提供类型注释/断言。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.