TypeScript 函数类型,如Array.prototype.reduce或RxJS的"scan",似乎不可能,

hpcdzsge  于 3个月前  发布在  TypeScript
关注(0)|答案(8)|浏览(49)

Bug报告

我搜索了,但找不到相关的issue(但我觉得很可能不存在这样的问题)。似乎应该有一个标签为“设计限制”之类的东西?
基本问题是,如果你使用一个与reducer返回值不同的标准“reducer”回调模式,返回的类型就是不正确的。

// The type of result is `number`, but should be `string`.
const result = [1, 2, 3].reduce((acc, value) => {
//                              ~~~~~~~~~~~~~~~~~
//                                 ^-- Error Here: "No overload matches this call"
    if (acc === null) {
        return '' + value;
    } else {
        return acc + ', ' + value;
    }
}, null);

console.log(result); // "1, 2, 3"
console.log(typeof result); // "string"

🔎 搜索词

  • is:issue is:open array reduce
  • is:issue is:open reduce callback
  • is:issue is:open reduce "No overload matches this call"
  • is:issue is:open reduce label:"Design Limitation"

🕗 版本与回归信息

任何版本的TS,到TMK。具体在4.2和4.4.4中测试过。

  • 我尝试了每个版本,并尽可能查阅了FAQ中的条目。显然有很多条目。我没有看到任何相关的。

⏯ Playground链接

Playground中的示例

💻 代码

// The type of result is `number`, but should be `string`.

const result = [1, 2, 3].reduce((acc, value) => {
//                              ~~~~~~~~~~~~~~~~~
//                                 ^-- Error Here: "No overload matches this call"
    if (acc === null) {
        return '' + value;
    } else {
        return acc + ', ' + value;
    }
}, null);

console.log(result); // "1, 2, 3"
console.log(typeof result); // "string"

🙁 实际行为

即使编译后的JavaScript是有效的,也会报类型错误。

🙂 预期行为

没有类型错误,result 应该是一个类型 string,而不是 number

相关:

ReactiveX/rxjs#6649

hmmo2u0o

hmmo2u0o1#

这是一个推理问题,而不是打字问题:https://www.typescriptlang.org/play?#code/PTAEBUAsFNQFwJ4AdYHsBmoBO0DOBXAGzlAEtdQADAO3wFsAjaLSgGlAfxN0lSIBMOsSrjhZS1AOaUAdAFgAUIoDGqaqOx4iJALygA2gEZ2AJnYBmALoyc-fMugAeWoUKgAPqFHipAPgAU-gCGysrsAG5BhPjQAJSgOr6gAN6KIKAZmVnZObkZAH6FRcUlhWlgeZVV2QB6ALR1oACiWFioWKAAEszQAFygAEQAcqigqOHMhKhBgnRBcMowFHCQ5KDKUYQDilmkmMGhCTp6LoTxqQp5OHD4WNSgAOQPoADUoJHR0ADcO5kAvqBoIRcLALldoDc7qAQspXo92M83h8Yj9Lv9FH92KdYqiVGpcKhCNAZFNJP4cARiDjQOkBsZQGZQOZtgpVOpCcTSf5ECgMJpKXBqbTvBJJAMgA

翻译结果为:这是一个推理问题,而不是打字问题。

h7appiyu

h7appiyu2#

I think this is covered by #36554 . I usually handle this with something similar to @nmain’s example, by adding an annotation on the accumulator. https://www.typescriptlang.org/play?#code/PTAEBUAsFNQFwJ4AdYHsBmoBO0DOBXAGzlAEtdQADAO3wFsAjaLSgGlAfxN0lSIBMOsSrjhZS1AOaUAdAFgAUIoDGqaqOx4iJALygA2gEZ2AJnYBmALoyc-fMugAKRwENlygFyhahQqAA+oKLiUuwAbi6E+NAAlKA6AHygAN6KoOlkmK7u8Tp6PoRxqQoZpZpw+FjUoADkNaAA1KARUdAA3GkZAL6g0IS4sMVlGTgVVaBuyo217PVNLdEdJd2KXewFMUsqariohNAyhKiSjjgExJugIKAARMagZqDmN9vqewdHJ4goGJrncJdrjdghJJDcgA

b09cbbtk

b09cbbtk3#

#36554中有一些关于reduce的提及,但我建议你将这个示例也复制到那里以获得更多的覆盖范围。谢谢!

zlhcx6iw

zlhcx6iw4#

我们的推断过程在这里有着根本性的不同,但当我们有普通的局部语句时,我们能够深入到这个循环的主体中,并知道它已经被设置为 stringnull

function foo(xs: number[]) {
    let acc = null;
    for (const value of xs) {
        if (acc === null) {
            acc = "" + value;
        }
        else {
            acc = acc + ", " + value;
        }
    }
    return acc;
}

我想在某个时候,我想请教一下 @ahejlsberg ,了解如何处理这些循环控制流分析(我已经有一段时间没有接触了,所以我基本上已经忘记了)。也许我们可以就一些更适合这种高阶函数的东西进行头脑 Storm 。我们推断过程的一个问题是,在我们第一次遍历参数时,我们“锁定”了那些不是“上下文敏感”的表达式的推断(例如回调函数)。这里的 null 是唯一一个不是上下文敏感的东西,所以它赢了。我们可以尝试对这个过程进行调整,但是

  • 它会变得更复杂
  • 对于每个重载函数来说,可能会需要更多的工作
  • 它可能会破坏今天存在的大量代码
eivnm1vs

eivnm1vs5#

这个问题已经被标记为"重复",并且最近没有活动。它已经自动关闭,以进行维护。

o2gm4chl

o2gm4chl6#

是的,我对@nmain的方法很熟悉。然而,我强烈建议人们利用推断而不是传递显式泛型。我说这个是因为泛型只是API签名中的另一个元素,可以在主要版本之间发生变化。所以reduce((acc: null | string, item) =>会成为我的首选,而不是<null | string>。这意味着泛型是隐式的,你不必担心它们在发布中是否会发生改变。

myzjeezk

myzjeezk7#

老实说,@DanielRosenwasser,我更希望这个问题保持开放,因为我觉得这个问题是reduce类型的回调情况中独一无二的。正如我们在RxJS的scan中所看到的,这显然与数组方法无关。在这里无法正确进行推断,我亲眼看到过数百个例子,人们用不准确的显式类型来实现这个功能,结果却是自讨苦吃。

siv3szwd

siv3szwd8#

@benlesh,你是在询问是否可以自动进行联合类型提升吗?

因为在示例中展示的代码里,没有什么是不可能的。用户可以通过以下方式轻松地修复类型错误:

  • 将默认值替换为 ''(空字符串),而不是 null - 这样所有三种类型(累加器、返回值、默认值)都是相同的,整个代码会有一个漂亮的签名 reduce<A, T>((a: A, v: T) => A, default: A
  • 当然,这会导致需要重写依赖于 null 的函数体代码。这对于你展示的例子(if(!acc))来说不是问题,但对于更复杂的实际代码可能会更棘手。然而,原始问题在于在一个应该只有一个类型的场景中使用了两个类型。

https://www.typescriptlang.org/play?#code/PTAEBUAsFNQFwJ4AdYHsBmoBO0DOBXAGzlAEtdQADAO3wFsAjaLSgGlAfxN0lSIBMOsSrjhZS1AOaUAdAFgAUIoDGqaqOx4iJALygA2gEZ2AJnYBmALoyc-fMugAKRwENly9gDcXhfNACUoDoAfKAA3oogoNExsXHxCdEAfimpaekpkWCJOblxAHoAtIWgAKJYWKhYoAASzNAAXKAARAByqKConsyEqC6CdC5wyjAUcJDkoMo+hM2KsaSYru5BOnq0hISBEQqJOHD4WNSgAOQnoADUoN6+0ADc8zEAvqDQhLiwO3vQB0egbspLqd2Ocrjc-A9ds9FE8QSd-JCVGpcKhCNAZL1JI4cARiAjQFFmsZQGZQOY5gpVOpUejMY5ECgMJpcXB8YTROIpM0gA
ps:我知道很多人可能不同意放弃使用 null 作为默认值,所以那些可以在 reduce/scan 函数调用末尾使用 , null as null|string); 类型。

const result = [1, 2, 3].reduce((acc, value) => {
    if (acc === null) {
        return '' + value;
    } else {
        return acc + ', ' + value;
    }
}, null as null|string);

Typescript 如何(以及为什么应该)将 null 提升为联合类型 - 这是另一个问题,不过.. reduce<A,T,D>(a:A|D, v:T) => A, default: A|D) 看起来已经很混乱了,如果这种提升只会针对 null 进行特殊处理(通过条件类型),那么它就变得几乎神奇(用不好的话说)。然而,如果这能帮助人们更准确地描述他们的代码 - 那么或许这就是前进的方向🤷

相关问题