Typescript 中奇怪的类型推断行为
Weird Type Inference Behaviour in Typescript
假设此代码:
// base class:
class Pin<Output, Input=Output> {
to<Out>(pin: Pin<Out, Output>): Pin<Out, Output> {
return pin;
}
from<In>(pin: Pin<Input, In>): Pin<Input, In> {
return pin;
}
}
//some type aliasing for convenience:
type SyncFunc<I, O> = (i: I) => O;
type Resolve<T> = SyncFunc<T, void>;
type AsyncFunc<I, O> = (i: I, cb: Resolve<O>) => void;
type Func<I, O> = SyncFunc<I, O> | AsyncFunc<I, O>;
function src<Type>(): Pin<Type> { return new Pin<Type>(); }
function map<I, O>(m: SyncFunc<I, O>): Pin<O, I>;
function map<I, O>(m: AsyncFunc<I, O>): Pin<O, I>;
function map<I, O>(m: SyncFunc<I, O> | AsyncFunc<I, O>): Pin<O, I> {
return new Pin<O, I>();
}
使用此设置,以下代码将出错:
src<number>().to(map((i, c: Resolve<number>) => c(i * 2))).to(map(x => x + 1));
由于未正确推断 i
的类型。
我可以像这样更改重载签名安排:
function map<I, O>(m: AsyncFunc<I, O>): Pin<O, I>;
function map<I, O>(m: SyncFunc<I, O>): Pin<O, I>;
function map<I, O>(m: SyncFunc<I, O> | AsyncFunc<I, O>): Pin<O, I> {
return new Pin<O, I>();
}
这修复了上一个示例的问题,但导致了这个问题:
src<number>().to(map(i => i * 2)).to(map(x => x + 1));
因为传递给 map()
的函数现在被假定为 AsyncFunc
类型,return 类型未被解析,因此 x
被假定为类型 unknown
,这会导致另一个错误。
想在 Typescript 的 GitHub 上开一个问题,但想先在这里问一下,以确保我没有遗漏任何东西。这是预期的行为吗?即它是 Typescript 类型推断的错误,还是它目前缺少的功能,或者我是否遗漏了什么?
您 运行 遇到的主要问题是 x => x + 1
和 i => i * 2
等值与 both[=94= 可能令人惊讶的类型兼容性] SyncFunc<number, number>
和 AsyncFunc<number, number>
:
const sf: SyncFunc<number, number> = x => x + 1; // okay
const af: AsyncFunc<number, number> = x => x + 1; // also okay... what?!
这是按预期工作的,但让足够多的人感到困惑,因为它是 TypeScript FAQ 的一部分。我会根据这个问题调整那里的信息:
问:x => x + 1
如何赋值给AsyncFunc
,前者接受一个参数,而后者需要两个参数? How could a function of one parameter be assignable to a function of two parameters?
A:x => x + 1
是 AsyncFunc
的有效赋值,因为它可以安全地忽略额外参数。在运行时,(x => x + 1)(10, "randomExtraParam")
有效。在编译时将其作为一个错误最终会使许多方法的常规使用无效,例如 Array.forEach()
或 Array.map()
,这些方法的实现将多个参数传递给回调,但通常与单个参数的回调一起使用。您可以阅读上面链接的常见问题条目,了解为什么他们认为这是最好的行为。
问:当return是number
时,x => x + 1
如何赋值给AsyncFunc
(假设x
被推断为number
) 和后者 returns void
? How could a function returning a value be assignable to one that doesn't?
A: void
return 类型意味着调用者不能 期望 一个 return 值,但这并不意味着绝对不会有。它只是意味着调用者应该忽略任何 returned 的值。如果你忽略了我给你的任何 return 值,那么我 return 1
而不是 return undefined
应该无关紧要。将此作为一个错误最终会使许多不参考回调的 return 值的回调接受函数的常规使用无效,例如 Array.forEach()
。像 a => arr.push(a)
这样的一些回调有副作用和 return 值,并且禁止 return 值会使以常规方式使用它们变得更加困难。
有鉴于此,这种行为是可以理解的;编译器无法可靠地区分 AsyncFunc<number, number>
和 SyncFunc<number, number>
。要做到这一点,您可能应该将类型更改为不兼容。一种方法是将 AsyncFunc
的 return 类型设为 void
以外的类型,例如 undefined
:
type Resolve<T> = SyncFunc<T, undefined>;
type AsyncFunc<I, O> = (i: I, cb: Resolve<O>) => undefined;
它们很相似,因为没有 return someValue
语句的函数将以 returning undefined
结束,但如果你 return 编译器现在会不高兴1
而不是 AsyncFunc
.
中的 undefined
这还是没有解决按参数个数区分函数的问题。如果你只是把 map()
的调用签名写成 <I, O>(m: Func<I, O>): Pin<O, I>
,从技术上讲,编译器有足够的信息可以看出 x => x + 1
一定是 SyncFunc
,但显然信息太多了立即推断:它需要推断类型参数 I
和 x
的类型,但它们相互依赖并且它放弃了。有时您可以让它工作,但并不总是值得付出努力。有关当前类型推理算法的局限性和改进它的可能性的问题和讨论,请参阅 microsoft/TypeScript#30134。
但是现在,您已经有了一个按照您想要的方式运行的解决方法:使用 overloads 引导编译器首先认真努力将 x => x + 1
解释为 AsyncFunc
,让它失败(因为不兼容的 return 类型)然后尝试 SyncFunc
并成功。这与之前关于 I
和 x
的问题相同,但类型 SyncFunc
显然足够简单,可以工作(与 Func
相比)。
这意味着以下内容适合您:
function map<I, O>(m: AsyncFunc<I, O>): Pin<O, I>;
function map<I, O>(m: SyncFunc<I, O>): Pin<O, I>;
function map<I, O>(m: Func<I, O>): Pin<O, I> {
return new Pin<O, I>();
}
src<number>().to(map((i, c: Resolve<number>) => c(i * 2))).to(map(x => x + 1));
src<number>().to(map(i => i * 2)).to(map(x => x + 1));
好的,希望对您有所帮助;祝你好运!
假设此代码:
// base class:
class Pin<Output, Input=Output> {
to<Out>(pin: Pin<Out, Output>): Pin<Out, Output> {
return pin;
}
from<In>(pin: Pin<Input, In>): Pin<Input, In> {
return pin;
}
}
//some type aliasing for convenience:
type SyncFunc<I, O> = (i: I) => O;
type Resolve<T> = SyncFunc<T, void>;
type AsyncFunc<I, O> = (i: I, cb: Resolve<O>) => void;
type Func<I, O> = SyncFunc<I, O> | AsyncFunc<I, O>;
function src<Type>(): Pin<Type> { return new Pin<Type>(); }
function map<I, O>(m: SyncFunc<I, O>): Pin<O, I>;
function map<I, O>(m: AsyncFunc<I, O>): Pin<O, I>;
function map<I, O>(m: SyncFunc<I, O> | AsyncFunc<I, O>): Pin<O, I> {
return new Pin<O, I>();
}
使用此设置,以下代码将出错:
src<number>().to(map((i, c: Resolve<number>) => c(i * 2))).to(map(x => x + 1));
由于未正确推断 i
的类型。
我可以像这样更改重载签名安排:
function map<I, O>(m: AsyncFunc<I, O>): Pin<O, I>;
function map<I, O>(m: SyncFunc<I, O>): Pin<O, I>;
function map<I, O>(m: SyncFunc<I, O> | AsyncFunc<I, O>): Pin<O, I> {
return new Pin<O, I>();
}
这修复了上一个示例的问题,但导致了这个问题:
src<number>().to(map(i => i * 2)).to(map(x => x + 1));
因为传递给 map()
的函数现在被假定为 AsyncFunc
类型,return 类型未被解析,因此 x
被假定为类型 unknown
,这会导致另一个错误。
想在 Typescript 的 GitHub 上开一个问题,但想先在这里问一下,以确保我没有遗漏任何东西。这是预期的行为吗?即它是 Typescript 类型推断的错误,还是它目前缺少的功能,或者我是否遗漏了什么?
您 运行 遇到的主要问题是 x => x + 1
和 i => i * 2
等值与 both[=94= 可能令人惊讶的类型兼容性] SyncFunc<number, number>
和 AsyncFunc<number, number>
:
const sf: SyncFunc<number, number> = x => x + 1; // okay
const af: AsyncFunc<number, number> = x => x + 1; // also okay... what?!
这是按预期工作的,但让足够多的人感到困惑,因为它是 TypeScript FAQ 的一部分。我会根据这个问题调整那里的信息:
问:x => x + 1
如何赋值给AsyncFunc
,前者接受一个参数,而后者需要两个参数? How could a function of one parameter be assignable to a function of two parameters?
A:x => x + 1
是 AsyncFunc
的有效赋值,因为它可以安全地忽略额外参数。在运行时,(x => x + 1)(10, "randomExtraParam")
有效。在编译时将其作为一个错误最终会使许多方法的常规使用无效,例如 Array.forEach()
或 Array.map()
,这些方法的实现将多个参数传递给回调,但通常与单个参数的回调一起使用。您可以阅读上面链接的常见问题条目,了解为什么他们认为这是最好的行为。
问:当return是number
时,x => x + 1
如何赋值给AsyncFunc
(假设x
被推断为number
) 和后者 returns void
? How could a function returning a value be assignable to one that doesn't?
A: void
return 类型意味着调用者不能 期望 一个 return 值,但这并不意味着绝对不会有。它只是意味着调用者应该忽略任何 returned 的值。如果你忽略了我给你的任何 return 值,那么我 return 1
而不是 return undefined
应该无关紧要。将此作为一个错误最终会使许多不参考回调的 return 值的回调接受函数的常规使用无效,例如 Array.forEach()
。像 a => arr.push(a)
这样的一些回调有副作用和 return 值,并且禁止 return 值会使以常规方式使用它们变得更加困难。
有鉴于此,这种行为是可以理解的;编译器无法可靠地区分 AsyncFunc<number, number>
和 SyncFunc<number, number>
。要做到这一点,您可能应该将类型更改为不兼容。一种方法是将 AsyncFunc
的 return 类型设为 void
以外的类型,例如 undefined
:
type Resolve<T> = SyncFunc<T, undefined>;
type AsyncFunc<I, O> = (i: I, cb: Resolve<O>) => undefined;
它们很相似,因为没有 return someValue
语句的函数将以 returning undefined
结束,但如果你 return 编译器现在会不高兴1
而不是 AsyncFunc
.
undefined
这还是没有解决按参数个数区分函数的问题。如果你只是把 map()
的调用签名写成 <I, O>(m: Func<I, O>): Pin<O, I>
,从技术上讲,编译器有足够的信息可以看出 x => x + 1
一定是 SyncFunc
,但显然信息太多了立即推断:它需要推断类型参数 I
和 x
的类型,但它们相互依赖并且它放弃了。有时您可以让它工作,但并不总是值得付出努力。有关当前类型推理算法的局限性和改进它的可能性的问题和讨论,请参阅 microsoft/TypeScript#30134。
但是现在,您已经有了一个按照您想要的方式运行的解决方法:使用 overloads 引导编译器首先认真努力将 x => x + 1
解释为 AsyncFunc
,让它失败(因为不兼容的 return 类型)然后尝试 SyncFunc
并成功。这与之前关于 I
和 x
的问题相同,但类型 SyncFunc
显然足够简单,可以工作(与 Func
相比)。
这意味着以下内容适合您:
function map<I, O>(m: AsyncFunc<I, O>): Pin<O, I>;
function map<I, O>(m: SyncFunc<I, O>): Pin<O, I>;
function map<I, O>(m: Func<I, O>): Pin<O, I> {
return new Pin<O, I>();
}
src<number>().to(map((i, c: Resolve<number>) => c(i * 2))).to(map(x => x + 1));
src<number>().to(map(i => i * 2)).to(map(x => x + 1));
好的,希望对您有所帮助;祝你好运!