高阶函数参数类型的错误类型推断

Posted

技术标签:

【中文标题】高阶函数参数类型的错误类型推断【英文标题】:Wrong type inference of higher-order function argument type 【发布时间】:2020-01-24 18:41:28 【问题描述】:

假设我想编写一个函数,它接受某种类型 T 的对象和另一个值,该类型 P 应该以某种方式受 T 限制,例如 P 应该是 T 的键数组。

我可以很容易地写出来:

function bar<T, P extends keyof T>(obj: T, p: P[]) 
  // use p to index obj somehow
  return obj;


bar( a: 1, b: 'foo' , ['a']); // Ok
bar( a: 1, b: 'foo' , ['a', 'b']); // Ok
bar( a: 1, b: 'foo' , ['a', 'b', 'c']); // Error: 'c' is not valid key

想象一下,然后我想将该函数用作高阶方法的参数,它应该与第二个参数 arg 一起接受它,然后用 thisarg 调用它:

class Indexed 
  constructor(public a: number = 1) 
  public app<P>(f: (obj: this, arg: P) => this, arg: P) 
    return f(this, arg);
  


const a = new Indexed().app(bar, ['a']); // Error, `Type 'string' is not assignable to type '"a" | "app"'.`
const a = new Indexed().app(bar, ['wtf']); // The same

如果我直接使用bar,一切都会按预期进行:

bar(new Indexed(), ['a']); // Ok
bar(new Indexed(), ['wtf']); // Err, as expected

Playground

问题是:如何编写app 使其接受/拒绝参数的方式与bar 相同?

请注意,一般我不知道 bar 先验的限制,所以我不能用与 bar 相同的界限来限制 P

【问题讨论】:

【参考方案1】:

我认为这只是 TypeScript 将 ["foo","bar"] 扩展为 string[] 的一种情况,因为它没有意识到您需要该类型来保持字符串文字元组 ["foo", "bar"](或至少是字符串文字数组 @ 987654326@)。在您的 bar() 函数中,P 被限制为 keyof 任何提示编译器不要将字符串文字扩展为字符串,但在 Indexed.app() 中不存在 P 的此类提示。

您需要想出一种方法来修改Indexed.app() 签名,以暗示P 应该在可能的情况下以狭窄的方式推断而无需实际限制它(因为您不知道P 将如您所说),或者您需要想出一种方法来提示/指定P 在您调用 Indexed.app() 时应该是狭窄的。


修改app() 的签名来做到这一点目前需要一些奇怪的技巧,除非这个changes,否则它看起来像这样:

type Narrowable =
  | string
  | number
  | boolean
  | symbol
  | object
  | undefined
  | void
  | null
  | ;

class Indexed 
  constructor(public a: number = 1) 
  public app<
    N extends Narrowable,
    P extends N | [] |  [k: string]: N | P | [] 
  >(f: (obj: this, arg: P) => this, arg: P) 
    return f(this, arg);
  


const a = new Indexed().app(bar, ["a"]); // okay
const b = new Indexed().app(bar, ["wtf"]); // error "wtf" not assignable to "a"|"app"

如果调用者记得这样做,调用站点的提示就不会那么难看:

class Indexed 
  constructor(public a: number = 1) 
  public app<P>(f: (obj: this, arg: P) => this, arg: P) 
    return f(this, arg);
  

const a = new Indexed().app(bar, ["a" as "a"]); // okay
const b = new Indexed().app(bar, ["wtf" as "wtf"]); // error "wtf" not assignable to "a"|"app"

或者你也可以忘记提示,自己手动指定类型参数:

const c = new Indexed().app<["a"]>(bar, ["a"]); // okay
const d = new Indexed().app<["wtf"]>(bar, ["wtf"]); // error "wtf" not assignable to "a"|"app"

好的,希望其中一个对您有所帮助。祝你好运!

Link to code

【讨论】:

以上是关于高阶函数参数类型的错误类型推断的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin函数 ⑤ ( 匿名函数变量类型推断 | 匿名函数参数类型自动推断 | 匿名函数又称为 Lambda 表达式 )

使用参数对函数进行类型推断

OCaml 类型推断不会产生函数参数的预期类型

Kotlin语法总结:函数类型和高阶函数

从函数回调推断泛型类型参数

swift 函数类型+高阶函数