Search code examples
programming-languagesschemetail-recursion

Broken Tail Recursion?


I have studied programming language course, I have this variant of Scheme code in the slide:

; (listof any) -> (listof any[no-cons])

(define (remove-pairs l)
  (cond
    [(empty? l) '()]
    [else
      (let ([ans
              (if (cons? (first l))
                  (begin (free! (first l))
                         (remove-pairs (rest l)))
                  (cons (first l)
                        (remove-pairs (rest l))))])
       (free! l) ; <- this will break our tail recursion.
       ans)]))

In the code, (free! l) will break our tail recursion. I don't know what does this function do that and I cannot get any sense.


Solution

  • It's impossible to tell what this function is supposed to do without seeing the code for free!. What can be inferred, is that the procedure deletes each element in the list that is not a cons cell, building and returning a new list with only the elements that were not cons cells from the start.

    It's incorrect to state that the function is tail recursive - it never was to begin with, no matter what free! does, the recursive calls are not in tail position.

    Here's the correct way to implement the inferred functionality using tail recursion, notice that the way the recursion was structured in the question was completely wrong and not tail recursive at all:

    (define (remove-pairs l)
      (let loop ((lst (reverse l))
                 (acc '()))
        (cond [(empty? lst)
               acc]
              [(cons? (first lst))
               (free! (first lst)) ; whatever that is
               (loop (rest lst) acc)]
              [else
               (loop (rest lst) (cons (first lst) acc))])))
    

    From a more practical point of view, you can get the same functionality using filter, although this is not guaranteed to be tail recursive:

    (define (remove-pairs l)
      (filter (lambda (x)
                (cond [(cons? x)
                       (free! x) ; whatever that is
                       #f]
                      [else #t]))
              l))
    

    Either way, this is how it works:

    (remove-pairs '(1 (2 4) 3 5 (6 8) 7 (9 11)))
    => '(1 3 5 7)