Recursive functions in computation expressions

后端 未结 2 2001
甜味超标
甜味超标 2021-01-13 02:16

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

2条回答
  •  心在旅途
    2021-01-13 03:05

    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.

提交回复
热议问题