Reputation: 1074
I'm using Racket but the question would apply to any Scheme, where tail recursion is supported.
I'm familiar with the traditional pattern for achieving tail recursion on a flat list, which is roughly like so:
(define (func x [acc null])
(if (null? x)
acc
(func (cdr x) (cons (do-something-to (car x)) acc))))
In this case, func
is in tail position.
But when I'm working with a tree, i.e., a list with recursively nested lists, I end up doing recursive descent with map
like so:
(define (func2 x)
(cond
[(atom? x) (do-something-to x)]
[(list? x) (map func2 x)]))
This works, but func2
is no longer in tail position.
Could you — and if so, how would you — rewrite func2
in a tail-recursive way?
(Leaving aside the question of whether it improves performance, which is not the question I'm asking.)
Upvotes: 1
Views: 407
Reputation: 4910
As it was already correctly stated and explained in the other answer, there's no advantage of using tail recursion on this. But, as you were interested in the way it would be done, here it is a deep-map
function I have implemented once. The code is cleaner if you're satisfied with the mirrored list.
(define deep-map
(λ (f lst)
(let tail-rec ([stack `(,lst)] [acc '(())])
;(displayln (~a "Stack: " stack " / Acc: " acc))
(cond [(null? (car stack))
(if (null? (cdr stack))
(car acc)
(tail-rec (cdr stack)
`(,(append (cadr acc) `(,(car acc))) . ,(cddr acc))))]
;; The first element is a list and is being put on the stack
[(list? (caar stack))
(tail-rec `(,(caar stack) . (,(cdr (car stack)) . ,(cdr stack)))
`(() . ,acc))]
;; Process next element
[else (tail-rec `(,(cdar stack) . ,(cdr stack))
`(,(append (car acc) `(,(f (caar stack)))) . ,(cdr acc)))]
))))
A simple example:
> (deep-map add1 '(1 ((2) 3)))
Stack: ((1 ((2) 3))) / Acc: (())
Stack: ((((2) 3))) / Acc: ((2))
Stack: (((2) 3) ()) / Acc: (() (2))
Stack: ((2) (3) ()) / Acc: (() () (2))
Stack: (() (3) ()) / Acc: ((3) () (2))
Stack: ((3) ()) / Acc: (((3)) (2))
Stack: (() ()) / Acc: (((3) 4) (2))
Stack: (()) / Acc: ((2 ((3) 4)))
'(2 ((3) 4))
Upvotes: 2
Reputation: 223133
You technically "can", by introducing an accumulator that acts as a stack. Your function would only be done when the stack is empty.
However, this has the same memory usage requirements as using the function call stack (i.e., non-tail recursion), so usually you don't gain anything by doing that.
Upvotes: 2