簡體   English   中英

Typescript在編譯時減去兩個數字

[英]Typescript subtract two numbers at compile time

原始問題

我需要一個實用程序類型Subtract<A, B>其中AB是數字。 例如:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM