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

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

试着用TypeScript弄湿我的脚,我一直遇到麻烦。今天重新出现了一个旧函数,就像一个练习一样,我很好奇是否可以将它转换为TypeScript。到目前为止,颈部完全疼痛。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

所以问题是具体的(见粗体)

1
const arrayCompare = (f: (_: Ord) => (_: Ord) => boolean) => ...

f期待该类型的高阶函数

1
Ord => Ord => boolean

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

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

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

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

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

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

但是来吧没有意义。我只能在这个上下文中访问f,而不是在我最终调用它时f将绑定的参数...

为什么我必须在TypeScript中为高阶函数参数提供名称?

这没有任何意义,使得函数签名变得冗长,丑陋,难以编写,更难以阅读。

UPDATE

"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" – Aleksey Bykov

我很高兴有机会回复此事。我可以用(number -> number -> number)签名来命名更多功能。

  • firstsecondmodminmax
  • 按位函数&|xor<<>>
  • (x, y) => sqrt(sq(x) + sq(y))
  • (x, y) => x + x + y + y + superglobalwhocares
  • 以及你可以梦想的任何其他功能

为了清理,我不建议函数参数本身不应该给出一个名称。我建议函数参数的参数不应该给出名字......

1
2
3
4
5
// this
func = (f: (number => number => number)) => ...

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

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

1
2
3
4
5
6
7
8
9
10
11
12
// 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的参数提供名称!因为谁知道我将使用哪种功能?所有这些都是合适的。

如果我试着在它们上面加上名字,我就会把用户想象的功能归结为......

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

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

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

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

"It would be quite confusing for function like:

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

What does it do?" - unional

同样的推理适用于此。除了(cb: (number, number) => (number, string) => boolean))是一个设计不佳的函数(你可以命名多少有用的混合型四元(4-arity)函数?)之外,它并不重要。 f不能假装知道任何关于使用这种签名我可以想出的无数功能的描述符。

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

行使

你能用有意义的名字取代_吗?

1
2
3
4
5
6
7
8
9
10
11
const apply2 = (f: (_: number) => (_: 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签名中?


至少以可读的方式编写currying定义很难。
我想做的是尽可能多地提取函数声明之外的签名,如下所示:

1
2
3
4
5
6
7
8
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]) => {
    ...
}

(游乐场代码)

我还删除了Ord类型别名之前的declare,没有必要。你可以找到更好的名称类型。
另一件事是你不需要在这里指定boolean

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

可:

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

或者您可以使用单个type声明来表达该函数。所有事情都考虑了可读性。

1
2
3
4
5
6
7
8
9
10
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) => {
   ...
};


当您指定(f: (Ord) => (Ord) => boolean)时,所有TypeScript都会看到您正在使用一个名为Ord的参数指定函数。您尚未指定类型。

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

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

考虑使用代码的时间:
image

它提供的语法与在TypeScript中定义函数的方式相同。即(name: Type) => ...。如果没有引入名称,则对用户来说会非常混乱。

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

对于以下功能来说会很困惑:

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

它有什么作用?


  • 这是在JS中使用函数的一种非常不切实际的方法
  • TS在泛型函数引用上很糟糕,所以Haskell的很多直觉在这里都不起作用
  • 回到你的问题,你必须提供名称,因为TS语法要求你这样做,理性的部分是当一个类型单独没有这样做时,参数的名称传达了额外的意义


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

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

    一致性

    这是在TypeScript中定义属性类型的方法:

    1
    2
    3
    4
    5
    class Person {
        public firstName: string;
        public lastName: string;
        public age: number;
    }

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

    1
    let person: Person;

    参数类型:

    1
    2
    function meet(who: Person) {
    }

    函数和方法返回类型:

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

    这是函数类型参数在没有参数名称的情况下的外观:

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

    ...要么...

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

    ...要么...

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

    这与上面的其他声明不太一致。

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

    this参数

    如果不在函数类型中指定参数名称,则在回调类型中指定this参数会变得更加混乱和不一致。从链接页面考虑此示例:

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

    你会想要这样:

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

    那么规则就是"在任何地方使用语法target: type,除了函数类型,它只是参数中的类型,除非有this参数,那么它实际上就是那种语法,但只在< x2>参数。"我并不责怪TypeScript设计师更倾向于使用"在任何地方使用语法target: type"这一规则。

    开发辅助工具

    当我将鼠标悬停在TypeScript中的函数上时,这就是我得到的:

    JQuery.each

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

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

    C#委托定义如下:

    1
    delegate void EventHandler(object sender, EventArgs e);

    Delphi函数变量:

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