簡體   English   中英

標記聯合的通用模式匹配

[英]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對應objM對應matcher 然后使用類型函數將match(obj, matcher)的返回類型計算為TM的函數......在這種情況下, ReturnType<M[T["kind"]]>

match()的實現中,編譯器似乎無法理解obj.kindT["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

看起來挺好的。 當然,世界上充滿了邊緣情況,因此這兩種方法之間無疑存在差異,這可能會使一種方法比另一種更適合您的用例。 無論如何,希望有所幫助; 祝你好運!

Playground 鏈接到代碼

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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