我在打字推论方面遇到了一个特殊的问题.

我目前正在开发一个pipe函数,它采取一系列操作,其中每个后续操作都依赖于前一个操作的结果. 此外,我还创建了一个辅助操作集合,如first,它从数组中 Select 第一项. 下面是一个精简的代码示例:

function pipe<T>(x: T[], fn: ((a: T[]) => T) | T):T {
    if (typeof fn === 'function') return fn(x);
    return fn;
}
function first<T extends any[]>(x: T):T[0] {
    return x[0];
}
const result1 = pipe(["s", "t"], first);
//    ^? any
const result2 = pipe(["s", "t"], (x) => x[0]);
//    ^? string

在这个特定的场景中,我们预计result1string,但实际上是any.不过,这不是Result2的问题.

我不知道有什么问题. 谁能告诉我我错过了什么?

完整的代码相当大,我这里有:TS Playground.


我对代码进行了一些过度简化,@jcalz中的 idea 不能完全解释我的问题. 这是一个更合适的代码片段:

function pipe<A, B>(x: A, fn1: ((a: [A]) => B) | B): B {
    return null as B;
}
function first<T>(x: [T[]]): T {
    return x[0][0];
}

const result1 = pipe(["s", "t"], first);
//    ^? const result1: unknown
const result2 = pipe(["s", "t"], (x) => first(x));
//    ^? const result2: string

我没主意了.

推荐答案

您的示例代码的主要问题是,当您调用pipe()时,当该函数本身是一个generic函数时,类型脚本不能从fn输入中正确地推断出T.TypeScrip缺乏针对泛型type unification的全类型推断算法,而这种算法会让它"插入"正确的泛型类型参数fn.这是Typescript 的一般限制,在microsoft/TypeScript#30134处描述.目前,任何依赖于高阶泛型类型操作的代码都可能在没有用户干预的情况下失败,例如手动instantiating相关的内部类型参数:

const result1 = pipe(["s", "t"], first<string[]>); 
//    ^?const result1: string    //   ^^^^^^^^^^ <- instantiate

根据您的用例,您可以通过更改编译器用来推断T的内容来解决此问题.对于fn处的T,现有的pipe()功能具有更高的优先级inference site.如果编译器首先try 从x输入推断T,那么它会表现得更好,因为["s", "t"]显然是string[]类型,没有任何 destruct 事物的泛型函数实例化问题.

如果我们可以说"嘿,请从x推断出T,而不是从fn推断出T",也就是说,我们可能会将fn中出现的T次标记为non-inferential type parameter usage.也许那会是

declare function pipe<T>(x: T[], fn: NoInfer<((a: T[]) => T) | T>): T;

这个microsoft/TypeScript#14829有一个开放的功能请求.目前,该语言不直接提供NoInfer<T>,但该问题描述了模拟或模拟NoInfer<T>的各种方法.

我们可以做的一件事是将第二个类型参数Uconstrain添加到第一个类型参数T中.然后我们使用U作为T的"非推理"版本.这是可行的,因为TU将被分别推断,并且编译器将判断UT.

对于pipe()人来说,这可能是:

function pipe<T, U extends T>(x: T[], fn: (((a: U[]) => U) | U)): T {
    if (typeof fn === 'function') return (fn as (a: T[]) => T)(x);
    return fn;
}

现在,当我们调用它的时候,事情就会按照我们所希望的那样进行:

const result1 = pipe(["s", "t"], first);
//    ^? const result1: string

如果你判断一下,你会发现T["s", "t"]推断为string,然后U也是string.可能像以前一样,U首先被推断为unknown,但后来失败了,它回落到T的约束,即string,因此判断first(a: string[]) => string),它成功了.

Playground link to code

Typescript相关问答推荐

当我点击外部按钮时,如何打开Html Select 选项菜单?

如何在TypeScrip面向对象程序设计中用方法/属性有条件地扩展类

如何在另一个参数类型中获取一个参数字段的类型?

在包含泛型的类型脚本中引用递归类型A<;-&B

我可以以这样一种方式键入对象,只允许它的键作为它的值吗?

对未到达受保护路由的路由环境做出react

TypeError:正文不可用-NextJS服务器操作POST

在类型脚本中创建泛型类型以动态追加属性后缀

剧作家元素发现,但继续显示为隐藏

常量类型参数回退类型

刷新时保持当前名称的Angular

从非文字数组(object.keys和array.map)派生联合类型

为什么发送空字段?

如何键入并类型的函数&S各属性

编剧- Select 动态下拉选项

数据库中的表请求返回如下日期:2023-07-20T21:39:00.000Z 我需要将此数据格式化为 dd/MM/yyyy HH:mm

组件的第二个实例使用 svelte 中第一个实例上设置的函数

Typescript - 确保 id 存在于数组中

错误:NG04002:无法匹配任何路由. URL 段:学生

当还传递了显式类型参数时,从函数参数推断类型