![](/img/trans.png)
[英]How to implement a type-level max function over a union of literals, in TypeScript?
[英]Typescript: type-level math when maping over a union type
是否可以通過 typescript 中的聯合類型對 map 使用類型級數學來生成一個新的聯合,即第一個聯合的 function?
例如,我想使用現有的聯合類型:
type foo = 768 | 1024 | 1280;
為了產生這個並集(每個選項除以 16):
type bar = 48 | 64 | 80;
如果工會中的成員數量是靈活的,也可以是:
600 | 768 | 1024 | 1280
對此的簡短回答是,目前在 TypeScript 中無法對數字文字類型執行任意數學計算。 在microsoft/TypeScript#26382上有一個(相當長期的)開放功能請求,要求這樣做。 您可能想在那里 go 並給它一個,並且由於它的狀態是“等待更多反饋”,如果您認為它令人信服,您可能想在此處留下詳細說明您的用例的評論。 但這可能不會有太大的不同,所以現在最好只是假設類型級別的數學不會很快發生。
您可以通過操縱[any, any, any]
之類的元組類型來說服編譯器執行某種數學運算。 元組類型具有數字文字的length
屬性,您可以使用可變元組類型和遞歸條件類型來更改元組長度。
這意味着您將僅限於對非負整數進行操作(您不能擁有長度為3.14159
或-2
的元組),並且由於類型遞歸的限制約為
25 級深度
1000 級深度(更新:對於尾遞歸條件類型,TS4.5 已將其提高到約 1000 個級別,因此現在情況有所好轉,但在數字方面仍然很小),很難處理大數字. 直觀的實現往往只適用於小於 1000 左右的數字。 有一些不太直觀的實現可以處理數千或數萬的數字(請參閱有關 GitHub 相關問題的評論),但即使這些實現也涉及實際構建這些長度的元組,因此可能會使編譯器陷入困境。 即使你非常聰明,你也可能會寫出復雜而脆弱的東西。 邊緣案例無處不在。
您的原始代碼示例是將小數除以二,這可以使用遞歸來完成,如下所示:
type DivideByTwo<N extends number, T extends 0[] = []> = N extends any ?
0 extends [...T, ...T, 0][N] ? T['length'] : DivideByTwo<N, [0, ...T]> : never;
type X = DivideByTwo<6 | 10 | 30>; // type X = 3 | 5 | 15
這通過取一個數字N
和一個以空 ( []
) 開始的元組T
來工作。 如果T
的兩個副本連接在一起,后跟單個元素在索引N
處有一個元素,那么T
至少是N
的兩倍,我們只返回T
的長度。 否則, T
太小,所以我們添加一個元素,然后再試一次。 這將小數字切成兩半:如果N
為10
,則T
變為[]
,然后[0]
,然后[0,0]
,然后[0,0,0]
,然后[0,0,0,0]
,然后[0,0,0,0,0]
滿足原始檢查,所以你得到5
。
N extends any...
部分使操作在N
中的聯合上分配,因此輸入的聯合成為輸出中的聯合...所以DivideByTwo<10 | 20>
DivideByTwo<10 | 20>
是5 | 10
5 | 10
(按某種順序)。
但是后來你改變了你的例子,將數字除以十六。 為了開始這樣做,我必須開始使用重復加倍元組的技巧(這意味着遞歸深度限制最終看起來像是對數字的對數而不是數字本身的限制)。 並且嘗試對除數(分子,大數)和除數(分母,這里的 16)都這樣做對我來說是不值得嘗試的。 這是一個硬編碼的東西,似乎可以除以十六:
type Quadruple<T extends any[]> = [...T, ...T, ...T, ...T]
type Explode<N extends number, R extends never[][]> =
Quadruple<R[0]>[N] extends never ? R : Explode<N, [[...R[0], ...R[0]], ...R]>;
type BinaryBuilder<N extends number, R extends never[][], B extends never[]> =
Quadruple<[...B, never]>[N] extends never ? B :
Quadruple<[...R[0], ...B]>[N] extends never ? BinaryBuilder<N, R extends [R[0], ...infer U] ? U extends never[][] ? U : never : never, B> :
BinaryBuilder<N, R extends [R[0], ...infer U] ? U extends never[][] ? U : never : never, [...R[0], ...B]>;
type CutTupleInFour<N extends number> = number extends N ? any[] : N extends number ?
Explode<N, [[never]]> extends infer U ? U extends never[][]
? BinaryBuilder<N, U, []> : never : never : never;
type DivideByFour<N extends number> = CutTupleInFour<N>['length']
type DivideBySixteen<N extends number> = DivideByFour<DivideByFour<N>>
type Foo = 768 | 1024 | 1280;
type Bar = DivideBySixteen<Foo>
// type Bar = 64 | 48 | 80
你喜歡? 是的,我也沒有。 即使詳細解釋它是如何工作的,我也無能為力。 我會說它通過兩次除以四來工作(當我直接嘗試十六時,編譯器陷入困境並且無法在合理的時間內完成),並且它通過建立重復加倍長度的元組來除以四,停止當它有一個太大時,然后將它們連接在一起形成一個合適的長度。
草圖:假設我們應該將80
除以 16。編譯1
首先將80
8
4
它構建長度為32
和16
的元2
。 然后它連接長度為16
和4
的長度為20
的長度。 現在它通過構建長度為8
、 4
、 2
和1
的元組來將20
除以 4。 然后它連接長度4
和1
以獲得長度5
之一。 結果是5
。
但是,糟糕,丑陋,可怕。 你不想在生產中做任何事情。
如果我是你,我會重新審視為什么你希望類型系統為你做這件事,而不是你自己做。 而且,如果您仍然有充分的理由,那么在 microsoft/TypeScript#26382 上進行游說可能比使用元組跳過障礙更有效。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.