如何在 Hindley-Milner 中表示具有多个参数的函数?

How to represent functions with multiple arguments in Hindley-Milner?

我正在阅读一个名为 Professor Frisby's Mostly Adequate Guide to Functional Programming 的函数式编程教程,作者介绍了 Hindley-Milner 和几个关于它的例子,其中之一是:

//  reduce :: (b -> a -> b) -> b -> [a] -> b
var reduce = curry(function(f, x, xs) {
  return xs.reduce(f, x);
});

reduce的第一个参数是一个函数,其类型签名是b -> a -> b,而这正是我不明白的部分。上面的代码是用js写的,也就是说f要带两个参数,return要带一个参数,像这样:

function f(b, a) {
  return b + a;
}

因此 f 的类型签名应该是 (b, a) -> b 而不是 b -> a -> b,对吗? f 不应该是一阶函数(由 b -> a -> b 暗示),至少在 js 中不是。

所以我的问题是,这是教程的错误吗?如果是这样,在 Hindley-Milner 中表示 (b, a) -> b 的正确方法是什么?

我自己完成了本教程,我认为通常假定函数是柯里化的,所以 fb -> a -> b 可能违反直觉但不一定是错误的 AFAIK。 (对我所说的一切持保留态度;我不是专家;)

然而 reduce 签名中 f 本身的括号为 reader 提供了重要线索(至少对 JavaScript reader) f 是一个函数:

reduce :: (b -> a -> b) -> b -> [a] -> b
          ^^^^^^^^^^^^^    ^    ^^^    ^
          1                2    3      4

1: f
2: reduction initial value
3: list to reduce
4: reduction result

由于 f 可以 进行柯里化,因此不能保证您会一次性收到所有参数。当然,在这种特殊情况下(减少函数),大多数人会期望 f 将同时应用于两个参数(累加值和值)。

函数签名仅仅是“意图声明”(至少在 JavaScript 中):

  • f 接受两个参数。
  • 第一个参数必须是 b 类型。
  • 第二个参数必须是 a 类型(顺便说一句,可以b 相同)。
  • return 值类型是 b 类型。

未定义函数应该做什么。

如果 b 是一个数字,那么 f 的这个定义就可以了 AFAIK:(尽管那将是一个非常无用的归约函数)

const f = b => a => 42

由于 xs 被注释为 [a],我们可以预期它是一个常规的 javascript 数组。

因此,.reduce调用reduce函数可以假定为Array.prototype.reduce

此数组方法不采用柯里化缩减函数。所以我会说:是的,类型注释是错误的。

要修复它,

  1. 按照您的建议将类型注释更改为 // reduce :: ((b, a) -> b) -> b -> [a] -> b,或者
  2. 更改实现以处理将柯里化函数传递给 Array.prototype.reduce:
//  reduce :: (b -> a -> b) -> b -> [a] -> b
var reduce = curry(function(f, x, xs) {
  return xs.reduce((b, a) => f(b)(a), x);
//                 ^^^^^^^^^^^^^^^^^
});