是否可以使用序列尾调用f#优化分组函数?

时间:2012-01-24 00:22:02

标签: f# tail-recursion tail-call-optimization

这是我的尝试,它不是尾部调用优化,因为我需要处理枚举器:

let Group func seed (items : seq<'t>) = 
    let rec some (i : IEnumerator<'t>) state = seq {
        try
            if i.MoveNext()
            then
                let newstate, iscomplete = func (i.Current) state
                if iscomplete
                then 
                    yield newstate
                    yield! some i newstate
            else
                yield state
        finally
            i.Dispose() }

    some (items.GetEnumerator ()) seed

以下是示例用法:

let buffer maxBufferSize items =
    Group (fun item state ->
        let newstate = [ item ] |> List.append state
        if newstate.Length >= maxBufferSize
        then (newstate, true)
        else (newstate, false)) List.empty items

如果我可以避免使用枚举器(即Seq.head AND Seq.tail),我可以使它工作但没有Seq.tail我不知所措。我真的希望用通用序列来完成这项工作..

我知道这段代码在当前状态下无效,因为我最终会多次处理枚举器。

1 个答案:

答案 0 :(得分:5)

您可以将try .. finally块从内部some函数(在每次迭代中输入的位置)移动到main函数。然后内部递归函数some变为尾递归:

let Group func seed (items : seq<'t>) =  
    // The handling of exceptions is done by the caller, 
    // so 'some' does not need to handle exceptions...
    let rec some (i : IEnumerator<'t>) state = seq { 
        if i.MoveNext() 
        then 
            let newstate, iscomplete = func (i.Current) state 
            if iscomplete then  
                yield newstate 
                // Recursive call in tail-call position
                yield! some i newstate 
        else 
            yield state }

    // Return a sequence that wraps the obtains the IEnumerator
    // and guarantees that it gets disposed if 'some' fails
    seq {
      let i = items.GetEnumerator ()
      try 
          // This is still not-tail recursive
          yield! some i seed 
      finally
          i.Dispose() }

甚至更好,您可以使用Group构造实现从use返回的序列:

    seq {
        use i = items.GetEnumerator ()
        // This is still not-tail recursive
        yield! some i seed } 

实际上,我认为这比原始代码更正确,因为它只调用Dispose方法一次。在您的版本中,每次执行进入some时都会调用一次(这取决于异常发生前处理的元素数量)。