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

时间:2017-02-19 00:26:09

标签: typescript higher-order-functions

尝试使用TypeScript让我的脚湿透,我一直跑进trouble。一个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: (_: Ord) => (_: Ord) => boolean) => ...

f期待类型

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

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

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

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

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

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

指定参数的名称
// now it's correct
(f: (_: Ord) => (_: Ord) => boolean)

但是c' mon没有任何意义。我只能在此上下文中访问f,而不是f在我最终调用它时将绑定的参数...

问题

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

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

更新

  

"就参数名称而言,考虑一个回调函数的函数 - > (数字 - >数字 - >数字) - >,所以完全根据您的选项类型:加,减,乘,除,幂,比较只有一个有意义,现在如果回调参数有一个name add :( number - > number - > number)选择很明显" - Aleksey Bykov

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

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

为了清理问题,我并未建议不应给出函数参数本身的名称。我建议不应该为函数参数参数指定名称......

// 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 ...)

如果我尝试的话,我无法在f中提供func个参数的名称!因为谁知道我将使用哪种功能?所有这些都是合适的。

如果我试图在其上加上名字,我就会将用户想象的功能归结为......

// 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没有提供任何添加的说明 - 我想这会将我带到我的点:他们并非意味着拥有名称或描述。 f对我们使用它的方式有知识,但这并不重要;只要它有(number => number => number)界面,我们关心的是所有。这是我们可以向func用户提供有关f参数的大多数有用信息。

  

&#34;对于像

这样的功能来说会很混乱
foo(cb: (number, number) => (number, string) => boolean)
     

它做了什么?&#34; - unional

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

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

运动

可以用有意义的名字替换_吗?

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签名中?

4 个答案:

答案 0 :(得分:1)

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

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

我还删除了declare类型别名之前的Ord,没有必要。你可以找到更好的名字 另一件事是你不需要在这里指定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) => {
   ...
};

答案 1 :(得分:0)

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

答案 2 :(得分:0)

当您指定(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)

它做了什么?

答案 3 :(得分:-1)

  

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

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

<强>一致性

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

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

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

let person: Person;

参数类型:

function meet(who: Person) {
}

函数和方法返回类型:

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;
}

那么规则就是“在任何地方使用语法target: type除了函数类型,它只是参数中的类型,除非那里是this参数,然后它实际上是该语法,但只在this参数中。“我并不责怪TypeScript设计师更倾向于使用规则“在任何地方使用语法target: type。”

发展辅助工具

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

JQuery.each

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

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

C#委托的定义如下:

delegate void EventHandler(object sender, EventArgs e);

Delphi函数变量:

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