TypeScript 中通用 ConsList 的类型错误

Type Error with Generic ConsList in TypeScript

我正在学习 TypeScript 的第 2 天,所以这可能是一个重复的问题(因为我可能不知道要搜索什么)。

我正在尝试做一些基本的 FP 练习来熟悉 TypeScript。这里我有一个带有构造函数的标准缺点列表类型:

type ConsList<T> =
    null |
    [T, ConsList<T>]

function cons<T>(head: T, tail: ConsList<T>): ConsList<T> {
    return [head, tail];
}

function fromArray<T>(arr: Array<T>): ConsList<T> {
    let xs: ConsList<T> = null;
    for (let i = arr.length - 1; i >= 0; i--) {
        xs = cons(arr[i], xs)
    }
    return xs;
}

还有一个文件夹:

function foldR<T, U>(
    f: ((x: T, acc: U) => U),
    acc: U,
    xs: ConsList<T>): U {

    if (xs === null) {
        return acc;
    }
    else {
        let [h, t] = xs
        return f(h, foldR(f, acc, t));
    }
}

当我尝试测试 rold 时,出现错误:

console.log(foldR((a, b) => cons(a, b), null, xs));
 fold.ts    60  29 error    2322   Type 'ConsList<number>' is not assignable to type 'null'.
   Type '[number, ConsList<number>]' is not assignable to type 'null'. ⮐ (typescript-tide)

非常感谢您指出我所缺少的内容!

我不确定我是否遵循了您的目标,但我看到了问题所在。

让我们从这个函数签名开始:

function foldR<T, U>(
    f: ((x: T, acc: U) => U),
    acc: U,
    xs: ConsList<T>
): U

具体看U。它是从参数 acc 中推断出来的,然后用在其他地方。

现在这里:

foldR((a, b) => cons(a, b), null, xs)

您通过 null 获得 acc。所以 U 被推断为 null.

现在打字稿试图强制执行该推理。这将我们带到:

f: ((x: T, acc: U) => U)

表示f是returns U的函数(推断为null)。但是,您的函数 returns ConsList<T> 可能是 null,也可能不是。函数类型不允许非空 return 值。这就是这个错误的意思:

Type 'ConsList<T>' is not assignable to type 'null'.
  Type '[T, ConsList<T>]' is not assignable to type 'null'.(2322)

null 是预期的 return 类型,ConsList<T> 是不兼容的类型。


要解决此问题,您需要能够获取 foldR 来推断 U 的正确类型。你有几种方法可以做到这一点。

您可以将 null 值转换为正确的联合类型。

console.log(foldR(
  (a, b) => cons(a, b),
  null as ConsList<T>,
  xs
));

或者您可以手动提供通用参数类型,因为您比推断的更了解:

console.log(foldR<T, ConsList<T>>(
  (a, b) => cons(a, b),
  null,
  xs
));

Working Typescript Playground


I'd tried type annotation let empty: ConsList<number> = null; which doesn't work, but let empty = null as ConsList<number> works. What's the difference?

Typescript 在这里有点太聪明了。如果你这样做:

const ltr: 'a' | 'b' = 'a'

然后 typescript 知道 ltr'a' | 'b',而且它应该缩小到 'a' 因为你分配给它一个常量值,它是该联合的成员.因此 Typescript 跟踪该细化并假设在该范围内 ltr 只能是 'a'.

但是,当您这样做时:

const ltr = 'a' as 'a' | 'b'

然后 'a' 被转换为一些包含 'a' 的超类型。 as 加宽了类型。并且因为加宽发生在右侧,加宽的类型被分配给变量。