我尝试使用以下协议定义函数:
[(1,2), (6,5), (9,10)] -> [3, 11, 19]
以下是我现在所拥有的:
fun sum_pairs (l : (int * int) list) =
if null l
then []
else (#1 hd(l)) + (#2 hd(l))::sum_pairs(tl(l))
根据类型检查器我有一些type mismatch
,但我无法弄清楚我到底错在哪里。
答案 0 :(得分:3)
此代码在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是curry,因此将它应用于函数会返回一个接受列表的新函数 - 在本例中是一个整数对列表。
但等一下!看起来这个匿名函数只是将加法运算符应用于它的参数!的确是。让我们摆脱它:
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)
必须在缺点(::
)之前发生,因此函数不是tail recursive。