F# 计算表达式中的递归函数

F# 计算表达式中的递归函数,f#,tail-recursion,computation-expression,F#,Tail Recursion,Computation Expression,首先是一些背景知识。我目前正在学习一些关于一元解析器组合器的知识。当我试图从(第16-17页)转换“chainl1”函数时,我想出了以下解决方案: let chainl1 p op = parser { let! x = p let rec chainl1' (acc : 'a) : Parser<'a> = let p' = parser { let! f = op let! y = p return!

首先是一些背景知识。我目前正在学习一些关于一元解析器组合器的知识。当我试图从(第16-17页)转换“chainl1”函数时,我想出了以下解决方案:

let chainl1 p op = parser {
  let! x = p
  let rec chainl1' (acc : 'a) : Parser<'a> =
      let p' = parser {
          let! f = op
          let! y = p
          return! chainl1' (f acc y)
          }
      p' <|> succeed acc
  return! chainl1' x
}
let chainl1 p op=parser{
设!x=p
让rec chainl1'(acc:'a):解析器=
让我们=
设b=解析器
b、 绑定(op)(乐趣f->
b、 绑定(p,(y->
b、 返回源(链1’(f acc y()()())))
p'acc
b、 从(链1'x)返回)

通常,由于“延迟”机制,即使使用多个
let!
绑定,也可以编写尾部递归计算表达式(请参见和)


在这种情况下,
chainl1
的最后一条语句会让您陷入困境。

在您的代码中,以下函数不是尾部递归的,因为在每次迭代中,它会在
p'
成功
之间做出选择:

// Renamed a few symbols to avoid breaking SO code formatter
let rec chainl1Util (acc : 'a) : Parser<'a> = 
  let pOp = parser { 
    let! f = op 
    let! y = p 
    return! chainl1Util (f acc y) } 
  // This is done 'after' the call using 'return!', which means 
  // that the 'cahinl1Util' function isn't really tail-recursive!
  pOp <|> succeed acc 

如您所知,新版本与此模式匹配,但原始版本与此模式不匹配。

这消除了用户代码中的递归,但在我的实现中,它仍然通过解析器实现本身进行堆栈溢出。现在,我将被激励调查“具有连续性的解析器”…Brian,我还使用了您的博客系列a这是一个学习的来源。它帮助了很多。同时我比较了Mau的答案(“seq”)使用我的解析器。我猜monad中的延迟方法是import。但我真的不知道。FParsec使用'while'…但我想使用一个函数解决方案:染料,我的直觉是,你需要重构底层解析器实现以获取continuation参数,
组合器将使用continuations对它的论点,有点像对…但在某个时候(从现在起很长一段时间,我现在的积压工作),我希望自己和博客能处理好所有的细节:)如果你能设法做到这一点,那将是非常棒的。我期待着它,并关注你的博客。
// Renamed a few symbols to avoid breaking SO code formatter
let rec chainl1Util (acc : 'a) : Parser<'a> = 
  let pOp = parser { 
    let! f = op 
    let! y = p 
    return! chainl1Util (f acc y) } 
  // This is done 'after' the call using 'return!', which means 
  // that the 'cahinl1Util' function isn't really tail-recursive!
  pOp <|> succeed acc 
let rec chainl1Util (acc : 'a) : Parser<'a> = 
  // Succeeds always returning the accumulated value (?)
  let pSuc = parser {
    let! r = succeed acc
    return Choice1Of2(r) }
  // Parses the next operator (if it is available)
  let pOp = parser {
    let! f = op
    return Choice2Of2(f) }

  // The main parsing code is tail-recursive now...
  parser { 
    // We can continue using one of the previous two options
    let! cont = pOp <|> pSuc 
    match cont with
    // In case of 'succeed acc', we call this branch and finish...
    | Choice1Of2(r) -> return r
    // In case of 'op', we need to read the next sub-expression..
    | Choice2Of2(f) ->
        let! y = p 
        // ..and then continue (this is tail-call now, because there are
        // no operations left - e.g. this isn't used as a parameter to <|>)
        return! chainl1Util (f acc y) } 
let rec foo(arg) = id { 
  // some computation here
  return! foo(expr) }