我是否必须在 TypeScript 中为高阶函数类型指定参数名称?

Do I have to specify parameter names for higher-order function types in TypeScript?

尝试使用 TypeScript 来弄湿我的脚,我将 运行 保留为 . An old function resurfaced today 作为练习,我很好奇是否可以将其转换为 TypeScript。到目前为止,它完全是脖子痛。

declare type Ord = number | string;

// type signature for f sucks really bad
// (f: Ord => Ord => boolean) would be really nice, if possible
// but instead I have to give names (_) for the parameters? dumb
const arrayCompare = (f: (_: Ord) => (_: Ord) => boolean) => ([x,...xs]: Ord[]) => ([y,...ys]: Ord[]): boolean => {
  if (x === undefined && y === undefined)
    return true;
  else if (! f (x) (y))
    return false;
  else
    return arrayCompare (f) (xs) (ys);
}

// here the names of the parameters are actually used
const eq = (x: Ord) => (y: Ord) : boolean => x === y;

// well at least it works, I guess ...
console.log(arrayCompare (eq) ([1,2,3]) ([1,2,3]));             // true
console.log(arrayCompare (eq) (['a','b','c']) (['a','b','c'])); // true

所以问题具体是关于(参见粗体

const arrayCompare = (f: <b>(_: Ord) => (_: Ord) => boolean</b>) => ...

f 期望类型为

的高阶函数
Ord => Ord => boolean

但是如果我使用这种类型签名

// danger !! unnamed parameters
(f: (Ord) => (Ord) => boolean)

TypeScript 将假定 Ord 作为参数的 name,隐含类型为 any

// what TypeScript thinks it means
(f: (Ord: any) => (Ord: any) => boolean)

当然这不是我想要的,但这就是我得到的。为了得到我真正想要的,我必须指定高阶函数的参数名称

// now it's correct
(f: (_: Ord) => (_: Ord) => boolean)

但是拜托,这没有任何意义。在这种情况下,我只能访问 f,而不能访问 f 在我最终调用它时将绑定的参数...

问题

为什么我必须为 TypeScript 中的高阶函数参数提供 names

它没有任何意义,并且使函数签名又长又丑,更难编写,更难阅读。


更新

"as far as names for parameters, consider a function that takes a callback of -> (number -> number -> number) ->, so based solely on the types your options are: add, subtract, multiply, divide, power, compare of which only one makes sense, now if a callback parameter had a name add: (number -> number -> number) the choice would be obvious"

我很高兴有机会回复这个问题。我可以用 (number -> number -> number) 签名命名更多函数。

澄清一下,我并不是说不应该为函数参数本身命名。我建议函数参数的 parameters 不应该命名...

// this
func = (f: (number => number => number)) => ...

// not this
func = (f: (foo: number) => (bar: number) => number)) => ...

为什么?好吧,因为 f 不知道我将提供的函数的参数。

// for the record, i would never name parameters like this
// but for those that like to be descriptive, there's nothing wrong with these
const add = (addend: number) => (augend: number) => number ...
const sub = (minuend: number) => (subtrahend: number) => number ...
const divide = (dividend: number) => (divisor: number) => number ...
const mult = (multiplicand: number) => (multiplier: number) => number ...

// I could use any of these with my func
func (add ...)
func (sub ...)
func (divide ...)
func (mult ...)

如果我尝试了,我无法在 func 中为 f 的参数提供名称!因为谁知道我会使用哪个功能?都合适。

如果我试图给它们起名字,我就把用户的想象力归为一类......

// maybe the user thinks only a division function can be specified (?)
func = (f: (dividend: number) => (divisor: number) => number) => ...

dividenddivisor 不适合这里,因为上面列出的任何函数都适合。 最好我可以做到这一点

// provide generic name for f's parameters
func = (f: (x: number) => (y: number) => number) => ...

但这有什么意义呢?它不像 xy 成为绑定标识符。 xy 没有提供额外的描述——我想这让我想到了 我的 点:它们不是 meant 有名称或描述。 f 对我们可能使用它的方式有 的了解,但这并不重要;只要它有一个 (number => number => number) 接口,那就是我们关心的 all。这是我们可以向 func 用户提供的关于 f 参数的 有用的信息。

"It would be quite confusing for function like:

foo(cb: (number, number) => (number, string) => boolean)

What does it do?" -

同样的推理也适用于此。除了 (cb: (number, number) => (number, string) => boolean)) 是一个设计不佳的函数这一事实(你能说出多少有用的混合类型四元(4 元)函数?), 无关紧要. f 不能假装知道关于我能想出的无数函数的任何描述符。

所以我的问题是,为什么我必须为函数参数参数指定公开无意义的名称?


运动

你能用有意义的名字替换 _ 吗?

const apply2 = (f: (<b>_</b>: number) => (<b>_</b>: number) => number) => (x: number) => (y: number): number => {
    return f (x) (y)
};

const sqrt = (x: number): number => Math.sqrt(x);
const sq = (x: number): number => x * x;
const add = (addend: number) => (augend: number): number => addend + augend;
const pythag = (side1: number) => (side2: number): number => sqrt(add(sq(side1)) (sq(side2)));

console.log(apply2 (add) (3) (4));    // 7
console.log(apply2 (pythag) (3) (4)); // => 5

如果不是,您能否提出一个令人信服的论据,说明为什么这些名称 必须 出现在您的 TypeScript 签名中?

  1. 这是一种在 JS 中使用函数的非常不切实际的方式
  2. TS 在函数引用上的泛型很糟糕,所以很多来自 Haskell 的直觉在这里不起作用

回到你的问题,你必须提供名称,因为 TS 语法要求你这样做,合理的部分是当类型本身不能这样做时,参数的名称传达额外的含义

当您指定 (f: (Ord) => (Ord) => boolean) 时,TypeScript 只会看到您指定的函数带有一个名为 Ord 的参数。您还没有指定类型。

编辑:我可以看出这是当前 TypeScript 的一个限制。在此处提交请求:https://github.com/Microsoft/TypeScript/issues/14173

为了支持这种语法,编译器(和语言服务)需要自己引入名称。

考虑使用代码的时间:

它提供的语法与在 TypeScript 中定义函数的语法相同。即 (name: Type) => ...。如果不引入名称,用户会很困惑。

另一方面,如果参数带有任何特定含义,IMO 值得提供参数名称,以便用户知道该怎么做。

像这样的函数会很混乱:

foo(cb: (number, number) => (number, string) => boolean)

它有什么作用?

很难编写柯里化定义,至少以一种可读的方式。
我会做的是尽可能多地提取函数声明之外的签名,像这样:

type Ord = string | number;
type ThirdFunction = (objs: Ord[]) => boolean;
type SecondFunction = (objs: Ord[]) => ThirdFunction;
type FirstFunction = (fn: (o: Ord) => (o: Ord) => boolean) => SecondFunction;

const arrayCompare: FirstFunction = f => ([x,...xs]) => ([y,...ys]) => {
    ...
}

(code in playground)

我还删除了您在 Ord 类型别名之前的 declare,不需要它。您可以为这些类型找到更好的名称。
另一件事是您不需要在此处指定 boolean

const eq = (x: Ord) => (y: Ord) : boolean => x === y;

可以是:

const eq = (x: Ord) => (y: Ord) => x === y;

或者您可以使用单个 type 声明来表达函数。考虑到所有因素,可读性相当不错。

type Ord = number | string;

type arrayCompareFunc = (f: (x: Ord) => (y: Ord) => boolean)
                      => (xs: Ord[])
                      => (ys: Ord[])
                      => boolean;

const arrayCompare: arrayCompareFunc = f => ([x,...xs) => ([y,...ys) => {
   ...
};

So my question is, why the heck do I have to specify overtly meaningless names for function parameter parameters ?

我不认为它们毫无意义。我可以想到 至少 命名参数有意义的三个很好的理由:

一致性

这就是您在 TypeScript 中定义 属性 类型的方式:

class Person {
    public firstName: string;
    public lastName: string;
    public age: number;
}

这是指定变量类型的方式:

let person: Person;

参数类型:

function meet(who: Person) {
}

函数和方法return类型:

function isUnderage(person: Person): boolean {
    return person.age < 18;
}

这是没有参数名称的函数类型参数的样子:

let myFunc: (string, string, number) => boolean;

...或...

function myFuncWithCallback(callback: (string, string, number) => boolean): void {}

...或...

type CallbackType = (string, string, number) => boolean;
let myFunc: CallbackType;
function myFuncWithCallback(callback: CallbackType): void {}

这与上面的其他声明不太相符。

在整个 TypeScript 中,只要你使用类型来表示目标的静态类型,你就会使用 target: type。这很容易记住。如果您开始制定如下规则:在定义函数类型的参数时,在所有情况下都使用语法 target: type 除了 ,这会使您的语言不那么一致,因此更难学习和使用采用。这在技术上可能不是必需的,但一致性本身就是一种价值。 JavaScript 充满了怪癖,TypeScript 继承了其中的很多。最好不要引入任何额外的不一致。 所以这不是一种罕见的模式,这是有充分理由的。

this 参数

如果不指定函数类型中的参数名称,指定 this parameters in callback types 会变得更加混乱和不一致。考虑链接页面中的这个例子:

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}

你会想要这样:

interface UIElement {
    addClickListener(onclick: (this: void, Event) => void): void;
}

那么规则就是 "Use the syntax target: type everywhere, except function types, where it is just the type in the parameters, unless there is a this parameter, then it actually is that syntax, but only in the this parameter." 我不怪 TypeScript 设计者宁愿遵循规则 "Use the syntax target: type everywhere."

开发辅助

这是我将鼠标悬停在 TypeScript 中的一个函数上时得到的结果:

您希望它如何读取 func: (number, Element) => any 而不是它具有的描述性参数名称?类型信息本身毫无用处。任何从此定义自动生成的代码都必须为参数提供无意义的名称,例如 param1param2。这显然不是最优的。

TypeScript 不是唯一一种命名函数类型参数的语言:

C# 委托定义如下:

delegate void EventHandler(object sender, EventArgs e);

Delphi 函数变量:

type TFunc = function(x: Integer, y: Integer): Integer;