如果我没记错的话,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
这就是我们所期望的:+ 是一个接受两个整数并返回一个新整数的函数,0 是一个整数,[1;2;3;4;] 是一个整数列表。 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) - 在相加的情况下这不是问题,因为它是关联的(这意味着 a+(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,我们不需要在 y 中添加 Number 构造函数:
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 列表。