问题描述
首先是一些背景.我目前正在学习有关Monadic解析器组合器的一些知识.当我尝试从本文(第16-17页),我想出了以下解决方案:
Some background first. I am currently learning some stuff about monadic parser combinators. While I tried to transfer the 'chainl1' function from this paper (p. 16-17), I came up with this solution:
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
}
我用一些大的输入测试了该函数,并得到了StackOverflowException.现在我想知道,是否可以重写使用某些计算表达式的递归函数,使其使用尾递归?
I tested the function with some large input and got a StackOverflowException. Now I am wondering, is it posible to rewrite a recursive function, that uses some computation expression, in a way so it is using tail recursion?
当我展开计算表达式时,我看不到通常如何实现.
When I expand the computation expression, I can not see how it would be generally possible.
let chainl1 p op =
let b = parser
b.Bind(p, (fun x ->
let rec chainl1' (acc : 'a) : Parser<'a> =
let p' =
let b = parser
b.Bind(op, (fun f ->
b.Bind(p, (fun y ->
b.ReturnFrom(chainl1' (f acc y))))))
p' <|> succeed acc
b.ReturnFrom(chainl1' x)))
推荐答案
在您的代码中,以下函数不是尾部递归的,因为-在每次迭代中-它都会在p'
或succeed
之间进行选择:
In your code, the following function isn't tail-recursive, because - in every iteration - it makes a choice between either p'
or succeed
:
// 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
根据解析器组合器的实现,以下重写可能有效(我不是专家,但值得尝试):
Depending on your implementation of parser combinators, the following rewrite could work (I'm not an expert here, but it may be worth trying this):
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) }
通常,用于在计算表达式内编写尾递归函数的模式有效.这样的事情会起作用(对于以允许尾递归的方式实现的计算表达式):
In general, the pattern for writing tail-recursive functions inside computation expressions works. Something like this will work (for computation expressions that are implemented in a way that allows tail-recursion):
let rec foo(arg) = id {
// some computation here
return! foo(expr) }
您可以检查到,新版本与此模式匹配,但原始版本则不匹配.
As you can check, the new version matches this pattern, but the original one did not.
这篇关于计算表达式中的递归函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!