我想实现一个{rust}函数,该功能类似于Rust的match keyword,但是针对TypeScript的结构类型系统进行了调整。
可以(但不符合人体工程学)为每个需要匹配的未命名的带标记的联合显式编写函数的类型。当前,最好的选择似乎是切换大小写,必须将其包装在match
中才能用作表达式。
(()=>{})()
一些所需的属性:
在理论上目前不可能实现这一点的情况下,我想知道为什么以及是否有一种正在进行的语言功能使之成为可能。
答案 0 :(得分:2)
结合了映射的类型和条件(利用内置的Extract):
type Cases<V extends {tag: string}, R> = {
[K in V['tag']]: (v: Extract<V, {tag: K}>) => R
}
function match<V extends {tag: string}, R>(value: V, cases: Cases<V, R>): R {
return (cases as any)[value.tag](value);
}
此签名将为丢失的案例提供一个错误,推断出该案例的区分值类型(仅在首先键入=>
时才在操场上自动完成),并在将丢失的案例指定为a时引发错误文字。
注意:由于分配了Shape
,对于您的shape
示例,它将推断出比const
更强的类型。在那种情况下,match
调用将引发有关额外情况circle
的错误,但是提供类型明确地使之静音。 match<Shape, string>(shape, {...})
要更好地推断返回类型,签名要复杂一些,但是可以做到:
type Cases<V extends {tag: string}> = {
[K in V['tag']]: (v: Extract<V, {tag: K}>) => any
}
type OnlyKnownCases<C, T> = C & Record<Exclude<keyof C, T>, "Unknown case"> & Record<any, Function>
function match<V extends {tag: string}, C extends Cases<V> = Cases<V>>(value: V, cases: OnlyKnownCases<C, V['tag']>): ReturnType<C[V['tag']]> {
return cases[value.tag](value);
}
引入泛型C
可以根据参数指定返回类型,以便将其推迟到呼叫站点。不幸的是,由于任何extends Cases<V>
都被允许,所以这会带来额外的情况。我们通过说任何不在有效标签(Exclude<keyof C, T>
上)的情况来限制这一点,该情况必须映射到字符串"Unknown case"
上,该字符串会比never
产生更好的错误。为了完整起见,我添加了& Record<any, Function>
,以便在非常奇怪的情况下,将case函数写为"Unknown case"
时,它不会绕过我们的限制。
ReturnType<C['someTag']>
给出someTag的case函数的返回类型。因此ReturnType<C[V['tag']]>
将处理所有案例的并集,以提供案例函数中所有返回类型的并集。