为什么这个函数不是尾递归的?

时间:2013-11-19 10:56:08

标签: .net f# lazy-evaluation tail-recursion

我想读取数据,因为它出现在文件中(数据由另一个进程写入)。像“连续阅读”这样的东西。

简单的代码只是将文件读到最后并完成工作而没有问题:

let readStream (r: StreamReader) =
  seq {
    while not r.EndOfStream do
      yield r.ReadLine() }

现在我向它添加以下逻辑:如果它到达流的末尾,它等待一秒钟,然后再次尝试。如果没有出现数据,它会在5次尝试中休眠2秒钟,最多持续5秒钟:

let readStream (r: StreamReader) =
    let rec loop waitTimeout waits =
        seq {
            match waitTimeout with
            | None -> 
                if not r.EndOfStream then 
                    yield r.ReadLine()
                    yield! loop None 0
                else yield! loop (Some 1000) waits
            | Some timeout when waits < 5 -> 
                let waits = waits + 1
                printfn "Sleeping for %d ms (attempt %d)..." timeout waits
                Thread.Sleep (timeout * waits)
                yield! loop None waits
            | _ -> ()
        }
    loop None 0

不幸的是,结果是非尾递归,应用程序很快崩溃了StackOverflow异常。

有人可以帮忙吗?

4 个答案:

答案 0 :(得分:1)

在进一步详细说明之前,您是否在启用调试时生成尾调用? (默认情况下,F#不会在调试代码中执行尾调用。)

编辑:嗯,您的返回值最终为seq {...},实际上不是loop x y的值。所以你真的在构建一个嵌套序列,而不是使用尾递归。

尝试以这样的方式重写代码,即不使用嵌套序列,而是在序列中使用尾递归。

答案 1 :(得分:1)

正如丹尼尔指出的那样,根据定义,一个具有序列表达式的主体的函数不是尾递归的,因为任何递归调用都会被延迟。这也保证了调用readStream不会导致堆栈溢出 - 它只会返回一个不透明的seq<string>值。因此,堆栈溢出的原因至少部分取决于调用代码。

答案 2 :(得分:1)

嗯,你写的代码也适合我(在F#3.0上)。正如kvb指出的那样,您的消费者代码是否存在问题?

type Reader(k) =
    let mutable n = 0
    member __.EndOfStream = n >= k
    member __.ReadLine() = n <- n + 1; "OK"

let readStream (r: Reader) =
    let rec loop waitTimeout waits =
        seq {
            match waitTimeout with
            | None -> 
                if not r.EndOfStream then 
                    yield r.ReadLine()
                    yield! loop None 0
                else yield! loop (Some 1000) waits
            | Some timeout when waits < 5 -> 
                let waits = waits + 1
                printfn "Sleeping for %d ms (attempt %d)..." timeout waits
                System.Threading.Thread.Sleep(timeout * waits)
                yield! loop None waits
            | _ -> ()
        }
    loop None 0

let test () =
    readStream (Reader 2000000)
    |> Seq.length

答案 3 :(得分:0)

我替换了(waitTimeout:TimeSpan选项)+模式匹配(waitTimeout:int)+ if ...然后问题已经消失。它可以在数百万个元素的seqs上工作,在常量RAM中。

糟糕的是我不知道它为什么会起作用。

let readStream (r: StreamReader) =
    let rec loop waitTimeout waits =
        seq {
            if waitTimeout = 0<ms> then
                let line = r.ReadLine()
                if line <> null then
                    yield line
                    yield! loop 0<ms> 0
                else yield! loop 1000<ms> waits
            elif waits < 5 then
                let waits = waits + 1
                Thread.Sleep (int waitTimeout * waits)
                yield! loop 0<ms> waits
        }
    loop 0<ms> 0