OCaml - 使用向左折叠的累加器

时间:2016-03-31 18:57:31

标签: functional-programming ocaml

here学习OCaml。

我想验证我是否已了解此OCaml代码片段的工作原理

List.fold_left (fun acc x -> acc + x) 0 [ 1; 2; 3; 4 ]

我有一种直觉,认为这相当于Python中的reduce函数。具体来说,我认为它相当于

reduce(lambda x, y: x + y, [1, 2, 3])

匿名函数采用两个参数 - accx,并返回单个值acc + x。我理解,最初,第一个参数acc将为0,但它如何知道第二个参数必须是列表的第一个元素?

我认为发生的事情是fold_left为匿名函数提供两个参数,然后用新参数递归调用自身,直到列表变空。

为了确认这一点,我看到this

当我定义像let inc x = x + 1这样的函数时,我会得到类似val inc : int -> int = <fun>的内容,但在这种情况下,签名为: ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a = <fun>

什么是'a,我应该如何解释此功能签名,以便List.fold_right f [a1; ...; an] b成为f a1 (f a2 (... (f an b) ...))

2 个答案:

答案 0 :(得分:5)

你问了很多问题。

我很确定Python reduce是一个折叠,所以你的直觉可能是正确的。

你问“怎么知道第二个参数必须是列表的第一个元素?”不幸的是,我认为这不是一个很好的问题。没有“它”知道任何事情。最有可能的答案是fold_left的定义。它知道该怎么做,因为有人写了这样的代码: - )

以下是标准库中fold_left的定义:

let rec fold_left f accu l =
  match l with
    [] -> accu
  | a::l -> fold_left f (f accu a) l

从某种意义上说,这应该回答你所有的问题。

'a类型中的fold_left类型是累加器的类型。关键是你可以使用你想要的任何类型的累加器。这就是折叠如此强大的原因。只要它与折叠函数接受和返回的值匹配,它就可以是你想要的任何东西。

答案 1 :(得分:2)

如果我没记错的话,reduce是一个更简单的fold版本,它将列表的第一个元素作为起始元素。我这样定义:

let reduce f = function 
   | x::xs -> fold_left f x xs
   | [] -> failwith "can't call reduce on empty lists!"

如果您在OCaml中输入,它将显示其类型:

val reduce : ('a -> 'a -> 'a) -> 'a list -> 'a 

您可以将其与fold_left的类型进行对比:

('b -> 'a -> 'b) -> 'b -> 'a list -> 'b

这里的类型变量'a'b意味着它们可以代表任何类型。在您的示例中,'a'b都变为int。如果我们插入类型,fold_left具有签名:

(int -> int -> int) -> int -> int list -> int

这就是我们所期望的:+是一个函数,它接受两个int并返回一个新的,0是一个int,[1;2;3;4;]是一个int列表。 fold_left有两个类型变量且reduce只有一个已经提示它更一般的情况。了解为什么我们可以查看reduce的定义。由于折叠的起始元素是列表的元素,因此'a''b类型必须相同。这对于总结元素很好,但是,我们想为我们的求和构造一个抽象语法树。我们为此定义了一种类型:

type exp = Plus of exp * exp | Number of int 

然后我们可以致电:

fold_left (fun x y -> Plus (x, (Number y))) (Number 0) [1; 2; 3; 4]

结果表达式为:

Plus (Plus (Plus (Plus (Number 0, Number 1), Number 2), Number 3), Number 4)

这棵树的一个好处是你可以很好地看到首先应用的是什么(0和1) - 如果添加这不是问题,因为它是关联的(这意味着+(b + c)=(a + b)+ c)不是减法的情况(比较例如5-(3-2)和(5-3)-2)。

如果你想用reduce做类似的事情,你会注意到OCaml抱怨类型错误:

reduce (fun x y -> Plus (x, (Number y))) [1; 2; 3; 4] ;; Error: This expression has type exp but an expression was expected of type
int

在这种情况下,我们可以将每个整数作为表达式包装在输入列表中,然后类型一致。由于我们已经有Numbers,因此我们不需要将Number构造函数添加到y:

let wrapped = map (fun x -> Number x) [1; 2; 3; 4] in
reduce (fun x y -> Plus (x, y)) wrapped

同样,我们有相同的结果,但我们需要对map进行额外的函数调用。在fold_left的情况下,这不是必需的。

P.S。:您可能已经注意到OCaml将fold_left的类型赋予('a -> 'b -> 'a) -> 'a -> 'b list -> 'a。我猜你会很快意识到类型变量的名称不起作用。为了便于比较,我切换了名称,使得该函数始终应用于'a列表。