如何在SML中将递归函数转换为尾递归函数?

时间:2015-08-17 09:59:38

标签: recursion sml tail-recursion

我正在尝试在SML中编写一个函数,该函数将返回列表元素的子序列,以及列表中其余元素的序列。 所以,我的功能是

fun subseq (left,right,0) = (left,right) |
    subseq (left,h::t,len) =
       let
          val (left,right) = subseq ([],t,len-1)
       in
          (h::left,right)
       end;

fun subseqMain (mainList, length) = subseq ([],mainList,length);

我的问题是如何通过将此函数转换为尾递归函数来提高此函数的效率,而不使用@运算符。

感谢您的时间!

1 个答案:

答案 0 :(得分:2)

最简单的方法是使用rev。基本上,这个想法是在每次迭代中使用left。你的左子序列将按相反的顺序排列,这就是rev进来的地方。不幸的是你必须在子序列上进行两次传递,但实际上没有任何其他事情可以做到快多了。您将实现的性能增益取决于您使用的编译器。 SML / NJ执行CPS转换,因此非堆尾调用会产生少量堆分配而不是堆栈分配,因此您需要支付一些GC开销。您可以在整个SML / NJ Basis中看到他们在一些地方做了这类事情,例如,partition的定义是

fun partition pred l = let
      fun loop ([],trueList,falseList) = (rev trueList, rev falseList)
        | loop (h::t,trueList,falseList) = 
            if pred h then loop(t, h::trueList, falseList)
            else loop(t, trueList, h::falseList)
      in loop (l,[],[]) end

你的另一个选择是手动将其转换为Continuation Passing Style(CPS),你基本上将“其余的计算”包装在一个函数中,我们称之为continuation。当一个函数完成时,它只是将其结果传递给传入的continuation。完整地解释CPS可能超出了这个答案的范围,所以我建议做一些谷歌搜索来真正掌握它。 partition示例可能如下所示:

fun partition pred l = let
    fun loop([], k) = k([], [])
       |loop(h::t, k) = loop(t, fn (tr, fl) => if pred h then k(h::tr, fl) else k(tr, h::fl))
    in loop(l, fn res => res) end

那就是说,如果你不打算转换为尾递归,那么有一种更直接的方式来实现你所拥有的东西:

fun subseq(l, 0) = ([], l)
   |subseq(h::t, i) = 
      let val (l, r) = subseq(t, i-1)
      in (h::l, r) end
   |subseq([], i) = raise Subscript