letrec map f l = match l with | [] -> [] | x :: xs -> let y = f x in y :: map f xs
这个实现的一个众所周知的限制是递归调用 map f xs 不在 尾部 位置。运行时需要记住在调用返回一个值 r 后继续执行 y :: r,因此这个函数在每次递归调用中都会消耗一定量的调用栈空间。 map f li 的栈使用量与 li 的长度成正比。对于在配置了有限栈空间的系统上的大型列表来说,这是一个正确性问题 - 令人讨厌的 Stack_overflow 异常。
#let with_stack_limit stack_limit f = let old_gc_settings = Gc.get () in Gc.set { old_gc_settings with stack_limit }; Fun.protect ~finally:(fun () -> Gc.set old_gc_settings) f ;;
val with_stack_limit : int -> (unit -> 'a) -> 'a = <fun>
type 'a two_headed_list = | Nil | Consnoc of 'a * 'a two_headed_list * 'a let[@tail_mod_cons] rec map f = function | Nil -> Nil | Consnoc (front, body, rear) -> Consnoc (f front, map f body, f rear)
由于 [@tail_mod_cons] 转换,对 f front 和 f rear 的调用将在 map f body 之前评估。特别是,这可能与未注释版本的评估顺序不同。(构造函数参数的评估顺序在 OCaml 中未指定,但许多实现通常使用从左到右或从右到左。)
let[@tail_mod_cons] rec map_vars f exp = match exp with | Var v -> Var (f v) | Let ((v, def), body) -> Let ((f v, map_vars f def), (map_vars[@tailcall]) f body)
请注意,结果函数不是尾递归函数,对 def 的递归调用将消耗堆栈空间。但是,表达式树往往是右倾斜的(许多 Let 按顺序排列,而不是嵌套在彼此内部),因此将对 body 的调用置于尾部位置是对朴素定义的有趣改进:如果我们假设 Let 构造的嵌套深度有限,它将提供有限的堆栈空间消耗。
使用冲突注释时也会出现错误,要求将两个构造函数参数置于尾部位置。
let[@tail_mod_cons] rec map_vars f exp = match exp with | Var v -> Var (f v) | Let ((v, def), body) -> Let ((f v, (map_vars[@tailcall]) f def), (map_vars[@tailcall]) f body)
let[@tail_mod_cons] rec flatten = function | [] -> [] | xs :: xss -> append_flatten xs xss and[@tail_mod_cons] append_flatten xs xss = match xs with | [] -> flatten xss | x :: xs -> x :: append_flatten xs xss
非递归函数也可以用 [@tail_mod_cons] 注释;这通常对递归函数的局部绑定很有用。
错误版本
let[@tail_mod_cons] rec map_vars f exp = let self exp = map_vars f exp in match exp with | Var v -> Var (f v) | Let ((v, def), body) -> Let ((f v, self def), (self[@tailcall]) body)
let[@tail_mod_cons] rec map_vars f exp = let[@tail_mod_cons] self exp = map_vars f exp inmatch exp with | Var v -> Var (f v) | Let ((v, def), body) -> Let ((f v, self def), (self[@tailcall]) body)
type 'a tree = Leaf of 'a | Node of 'a tree * 'a tree let[@tail_mod_cons] rec bind (f : 'a -> 'a tree) (t : 'a tree) : 'a tree = match t with | Leaf v -> f v | Node (left, right) -> Node (bind f left, (bind[@tailcall]) f right)
这里 f 是一个函数参数,而不是直接调用,当前的实现是严格的一阶,它不支持尾部模-cons 参数。在这种情况下,用户应该通过使用 (f[@tailcall false]) v 来表明他们意识到对 f v 的调用实际上不在尾部位置。
type 'a tree = Leaf of 'a | Node of 'a tree * 'a tree let[@tail_mod_cons] rec bind (f : 'a -> 'a tree) (t : 'a tree) : 'a tree = match t with | Leaf v -> (f[@tailcall false]) v | Node (left, right) -> Node (bind f left, (bind[@tailcall]) f right)
let rec map f l =
match l with
| [] -> []
| x :: xs ->
let y = f x in
let dst = y ::{mutable} Hole in
map_dps f xs dst 1;
dst
and map_dps f l dst idx =
match l with
| [] -> dst.idx <- []
| x :: xs ->
let y = f x in
let dst' = y ::{mutable} Hole in
dst.idx <- dst';
map_dps f xs dst' 1
(* if append_flatten_dps exists *)
and flatten_dps l dst i =
match l with
| [] -> dst.i <- []
| xs :: xss ->
append_flatten_dps xs xss dst i
(* if append_flatten_dps does not exist *)
and rec flatten_dps l dst i =
match l with
| [] -> dst.i <- []
| xs :: xss ->
dst.i <- append_flatten xs xss
OCaml 对“元组”函数进行了一个隐式优化,这些函数接受一个作为元组的单个参数:let f (x, y, z) = ...。使用元组文字参数(如 f (a, b, c))对这些函数的直接调用将通过直接传递参数来调用“元组”函数,而不是为它们构建元组。其他调用,无论是间接调用还是传递更复杂元组值的调用(如 let t = (a, b, c) in f t)都被编译为“不精确”调用,这些调用会通过包装器。
[@tail_mod_cons] 转换支持元组函数,但只会优化尾部位置的“精确”调用;对元组文字以外内容的直接调用不会变为尾调用。用户可以手动解包元组以强制调用为“精确”:let (x, y, z) = t in f (x, y, z)。如果对调用是否可以被尾部模态构造优化有任何疑问,可以使用 [@tailcall] 属性对被调用函数进行标注,如果转换不可行,则会发出警告。
letrec map (f, l) = match l with | [] -> [] | x :: xs -> let y = f x inlet args = (f, xs) in(* 此不精确调用无法尾部优化,因此将发出警告 *) y :: (map[@tailcall]) args