![](/img/trans.png)
[英]Is there a way to create tagged unions of arrays in TypeScript?
[英]Generic pattern-matching for tagged unions
我正在編寫一個用於標記聯合的通用模式匹配函數match
,將對象作為匹配器傳遞(用於代替典型的switch (obj.kind) { ... }
)。 這就是我所擁有的:
type UnionNamespace<Obj extends { kind: string }> = {
[K in Obj["kind"]]: Obj extends { kind: K } ? Obj : never;
};
type Matcher<Obj extends { kind: Kind }, Result, Kind extends string> = {
[K in Kind]: (obj: UnionNamespace<Obj>[K]) => Result;
};
function match<Obj extends { kind: Kind }, Result, Kind extends string>(
obj: Obj,
matcher: Matcher<Obj, Result, Kind>
): Result {
const fn = matcher[obj.kind];
return fn(obj as Parameters<typeof fn>[0]);
}
/* Example */
type Square = { kind: "square"; side: number };
type Circle = { kind: "circle"; radius: number };
type Shape = Square | Circle;
const square = { kind: "square", side: 2 } as Shape;
const surface = match(square, {
square: square => square.side ** 2,
circle: circle => Math.PI * circle.radius ** 2
});
console.log(surface.toFixed()); // Op that does not type-check if surface is not a number
我對代碼並不完全滿意。 例如,1) 我想不用提示Kind extends string
,但后來我得到Result=unknown
。 另外,2) 這個Parameters<typeof fn>[0]
看起來有點笨拙,但這是我發現對調用進行類型檢查的唯一方法。
任何想法/建議? 你知道任何現有的代碼可以做這樣的事情嗎?
[編輯] 最終版本,可選擇區分字段:
https://gist.github.com/tokland/c0db1473cc9bfa924470e52bdac8450c
我看不出你的代碼有什么問題; 甚至你有一個“不必要的”泛型類型參數的部分,因為它幫助編譯器推斷你想要什么。
您可以采取或離開的方法的可能修改是:
type Matcher<Obj extends { kind: string }> = {
[K in Obj["kind"]]: (obj: Extract<Obj, { kind: K }>) => any;
};
function match<T extends { kind: string }, M extends Matcher<T>>(
obj: T,
matcher: M
): ReturnType<M[T["kind"]]> {
const fn = matcher[obj.kind as T["kind"]];
return fn(obj as Parameters<typeof fn>[0]);
}
這里的想法是,編譯器是在推斷類型參數好得多X
從類型的值X
比它在推斷類型參數X
從類型的值SomeTypeFunction<X>
所以,我做了兩個類型參數: T
對應obj
, M
對應matcher
。 然后使用類型函數將match(obj, matcher)
的返回類型計算為T
和M
的函數......在這種情況下, ReturnType<M[T["kind"]]>
。
在match()
的實現中,編譯器似乎無法理解obj.kind
是T["kind"]
而不被提醒,但其他方面是相同的。 它在示例代碼中的行為與您的行為相似:
type Square = { kind: "square"; side: number };
type Circle = { kind: "circle"; radius: number };
type Shape = Square | Circle;
const square = { kind: "square", side: 2 } as Shape;
const surface = match(square, {
square: square => square.side ** 2,
circle: circle => Math.PI * circle.radius ** 2
});
console.log(surface.toFixed()); // toFixed() doesn't work if not a number
看起來挺好的。 當然,世界上充滿了邊緣情況,因此這兩種方法之間無疑存在差異,這可能會使一種方法比另一種更適合您的用例。 無論如何,希望有所幫助; 祝你好運!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.