此代码在 PolyML 5.2 中运行:
fun sum_pairs (l : (int * int) list) =
if null l
then []
else ((#1 (hd l)) + (#2 (hd l))) :: sum_pairs(tl l)
(* ------------^-------------^ *)
与您的差异很微妙,但意义重大:(#1 hd(l)) 与 (#1 (hd l)) 不同;前者并没有按照你的想法做——它试图提取hd 的第一个元组字段,这是一个函数!
既然我们在做,为什么不尝试重写该函数以使其更符合语言习惯呢?对于初学者,我们可以在函数头的参数上消除if 表达式和matching 的笨重元组提取,如下所示:
fun sum_pairs [] = []
| sum_pairs ((a, b)::rest) = (a + b)::sum_pairs(rest)
我们将函数分成两个子句,第一个匹配空列表(递归基本情况),第二个匹配非空列表。如您所见,这显着简化了功能,并且在我看来,它更易于阅读。
事实证明,将函数应用于列表元素以生成新列表是一种非常常见的模式。基础库提供了一个名为 map 的内置函数来帮助我们完成这项任务:
fun sum_pairs l = map (fn (a, b) => a + b) l
在这里,我使用anonymous function 将这些对添加在一起。但我们可以做得更好!通过利用currying,我们可以简单地将函数定义为:
val sum_pairs = map (fn (a, b) => a + b)
函数map 是柯里化的,因此将其应用于函数会返回一个接受列表的新函数——在本例中,是整数对的列表。
但是等一下!看起来这个匿名函数只是将加法运算符应用于它的参数!它的确是。让我们也摆脱它:
val sum_pairs = map op+
这里,op+ 表示一个应用加法运算符的内置函数,就像我们的函数字面量(上面)所做的那样。
编辑:后续问题的答案:
- 参数类型呢?看起来您已经完全消除了函数定义(标题)中的参数列表。是真的还是我遗漏了什么?
通常编译器能够infer 上下文中的类型。例如,给定以下函数:
fun add (a, b) = a + b
编译器可以很容易地推断出类型int * int -> int,因为参数涉及到加法(如果你想要real,你必须这样说)。
- 你能解释一下
sum_pairs ((a, b)::rest) = (a + b)::sum_pairs(rest)这里发生了什么吗?抱歉,这可能是个愚蠢的问题,但我只想完全理解它。特别是在这种情况下 = 是什么意思,以及这个表达式的求值顺序是什么?
这里我们在两个子句中定义一个函数。第一个子句sum_pairs [] = [] 匹配一个空列表并返回一个空列表。第二个,sum_pairs ((a, b)::rest) = ...,匹配以一对开头的列表。当您不熟悉函数式编程时,这可能看起来很神奇。但是为了说明发生了什么,我们可以使用case 重写从句定义,如下所示:
fun sum_pairs l =
case l of
[] => []
| ((a, b)::rest) => (a + b)::sum_pairs(rest)
将按顺序尝试子句,直到匹配。如果没有子句匹配,则引发 Match 表达式。例如,如果您省略了第一个子句,该函数将始终失败,因为 l 最终将是一个空列表(要么它从一开始就是空的,要么我们一直递归到最后)。
对于等号,它的含义与任何其他函数定义中的含义相同。它将函数的参数与函数体分开。至于评估顺序,最重要的观察是sum_pairs(rest)必须发生在cons之前(::),所以函数不是tail recursive。