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
}
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)))
In computability theory, a primitive recursive function is roughly speaking a function that can be computed by a computer program whose loops are all "for" loops (that is, an upper bound of the number of iterations of every loop can be determined before entering the loop).
Simple examples of a recursive function include the factorial, where an integer is multiplied by itself while being incrementally lowered. Many other self-referencing functions in a loop could be called recursive functions, for example, where n = n + 1 given an operating range.
They are of two types: indirect and direct recursion.
Recursion is a process of defining objects based on previously defined other objects of the same type. A recursion relation defines some rules and a few initial values to build up an entire class of objects.
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.
In general it is possible to write tail-recursive computation expressions (see 1 and 2), even with multiple let!
bindings thanks to the 'delay' mechanism.
In this case the last statement of chainl1
is what gets you into a corner I think.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With