F# 计算表达式中的递归函数
首先是一些背景知识。我目前正在学习一些关于一元解析器组合器的知识。当我试图从(第16-17页)转换“chainl1”函数时,我想出了以下解决方案: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!
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) }