Yes, the procedure is tail-recursive, meaning: wherever a recursive call is performed, it's the last thing that happens in that particular branch of execution, with nothing more to do after the recursion returns - hence, we say that the recursive call is in tail position.
This can be clearly seen if we rewrite the procedure using cond
instead of nested if
s, here you'll see that every branch of execution leads to either a base case or a recursive case, and all recursive calls are in tail position:
(define (remove n x)
; base case #1
(cond ((and (not (list? (car x))) (= n 1))
; return from base case, it's not recursive
(cdr x))
; recursive case #1
((not (list? (car x)))
; recursive call is in tail position
(remove (- n 1) (cons (list (car x)) (cdr x))))
; recursive case #2
((not (= n 1))
; recursive call is in tail position
(remove (- n 1) (cons (append (car x) (list(cadr x))) (cddr x))))
; base case #2
(else
; return from base case, it's not recursive
(append (car x) (cddr x)))))
For a more technical explanation of why the consequent/alternative parts of an if
special form can be considered tail-recursive, take a look at section 3.5 of the current draft of the Revised^7 Report on the Algorithmic Language Scheme - the language specification, here's a link to the pdf file (in essence the same considerations apply to R5RS, it's just that they're explained in more detail in R7RS). In particular:
If one of the following expressions is in a tail context, then the subexpressions shown as ⟨tail expression⟩ are in a tail context
...
(if ⟨expression⟩ ⟨tail expression⟩ ⟨tail expression⟩)
(if ⟨expression⟩ ⟨tail expression⟩)