我遇到了any在 Typescript 中似乎无法避免的情况。这是一个反映我正在尝试做的事情的示例:
type NativeFn<A, B> = {
kind: 'native'
fn: (a: A) => B
}
type ComposeFn<A, B, C> = {
kind: 'compose'
f: Fn<B, C>
g: Fn<A, B>
}
type Fn<A, B> = NativeFn<A, B>
| ComposeFn<A, any, B> // <=== HERE
function evalfn<A, B>(fn: Fn<A, B>, arg: A): B {
switch (fn.kind) {
case 'native': return fn.fn(arg)
case 'compose': {
// intermediate has type "any", which is a drag.
const intermediate = evalfn(fn.g, arg)
return evalfn(fn.f, intermediate)
}
}
}
Run Code Online (Sandbox Code Playgroud)
我想说的是,无论是哪种类型,ComposeFn<A, B, C>始终是 a ,但仍应键入。Fn<A, C>BB
使用any,我可以错误地输入以下内容:
const F: Fn<string, string[]> = { kind: 'native', fn: (n) => [n] }
const G: Fn<number, number> = { kind: 'native', fn: (n) => n + 1 }
const FoG: Fn<number, string[]> = {
kind: 'compose',
f: F,
g: G,
}
Run Code Online (Sandbox Code Playgroud)
unknown也不起作用。例子。
有什么办法可以完成我在这里要做的事情吗?
TypeScript 不直接支持存在类型(请参阅microsoft/TypeScript#14466)。如果是这样,您将使用存在类型<\xe2\x88\x83T> ComposeFn<string, T, number>来表示“有某种类型T,我的类型是ComposeFn<string, T, number>,但我不知道或不关心它是什么”:
// not valid TS syntax, do not try this\ntype Fn<A, B> = NativeFn<A, B> | <\xe2\x88\x83T> ComposeFn<A, T, B> \nRun Code Online (Sandbox Code Playgroud)\n\n如果没有对此类类型的直接支持,则有多种解决方法。到目前为止,最简单的一种是代替any存在类型,不用太担心。是的,这会导致不安全的事情发生,但 TypeScript 并不是真正的完美类型安全语言(请参阅microsoft/TypeScript#9825),也不是有意为之(请参阅TypeScript Design Non-Goal #3),而且any确实非常非常方便的。
Fn通过允许指定“存在”参数并将其默认为,您可能会获得一点额外的安全性any,如下所示:
type Fn<A, B, T = any> = NativeFn<A, B>\n | ComposeFn<A, T, B>\n\nfunction evalfn<A, B, T>(fn: Fn<A, B, T>, arg: A): B {\n switch (fn.kind) {\n case \'native\': return fn.fn(arg)\n case \'compose\': {\n const intermediate = evalfn(fn.g, arg)\n return evalfn(fn.f, intermediate)\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n它并不完美,但至少evalfn如果你做了一些太奇怪的事情,编译器会抱怨。
如果你真的想要类型安全,你可以通过使用泛型函数实现来模拟存在类型;如果我有一个像这样的函数签名<T>(x: T, y: T) => void,那么调用者可以选择T,并且实现者需要将其视为T存在类型参数。通过切换谁是调用者和谁是实现者,它为我们提供了一种奇怪的由内而外的方式来Fn<A, B>以类型安全的方式表示:
type SomeComposeFn<A, B> = {\n kind: \'compose\',\n <R>(go: <T>(f: Fn<T, B>, g: Fn<A, T>) => R): R\n}\n\ntype Fn<A, B> = NativeFn<A, B> | SomeComposeFn<A, B>\nRun Code Online (Sandbox Code Playgroud)\n\n在这里, aSomeComposeFn<A, B>本身就是一个函数,其作用类似于和函数Promise的a 。然后你可以像这样实现:fgevalfn
function evalfn<A, B>(fn: Fn<A, B>, arg: A): B {\n switch (fn.kind) {\n case \'native\': return fn.fn(arg)\n case \'compose\': {\n return fn((f, g) => {\n const intermediate = evalfn(g, arg)\n return evalfn(f, intermediate)\n });\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n如果你有一个原始ComposeFn<A, T, B>类型的值,你可以SomeComposeFn<A, B>以一种简单的方式将其转换为 a ,就像解析一个 Promise 一样:
function someComposeFn<A, T, B>(composeFn: ComposeFn<A, T, B>): SomeComposeFn<A, B> {\n return Object.assign(\n <R>(go: <T>(f: Fn<T, B>, g: Fn<A, T>) => R) => go(composeFn.f, composeFn.g),\n { kind: "compose" as const });\n}\nRun Code Online (Sandbox Code Playgroud)\n\n因此,这按照您想要的方式是完全类型安全的,但很麻烦,不是我可能会选择的方法。当然,这取决于你。
\n\n好的,希望能给您一些指导;祝你好运!
\n\n\n| 归档时间: |
|
| 查看次数: |
77 次 |
| 最近记录: |