We can follow the inspiration of John McCarthy's gopher
trick, i.e. do it by rotating the list (though, here, not surgically but virtually, i.e. using the arguments for that):
(define (flatten lst)
(flat (reverse lst) empty))
(define (flat lst rs)
(cond
((null? lst) rs)
((null? (car lst))
(flat (cdr lst) rs))
((not (pair? (car lst))) ;; atom? (car lst)
(flat (cdr lst)
(cons (car lst) rs)))
(else
(flat (append (reverse (car lst)) ;; NB
(cdr lst))
rs))))
There remains one point of contention which still has to be converted to the tail recursive call:
(list? a) =>
(flat (append (reverse a) d) rs) ;; NB
=
(define (flaprev a d rs)
(cond
((null? a) (flat d rs))
((null? (car a)) (flaprev (cdr a) d rs))
(else (flaprev (cdr a)
(cons (car a) d) rs))))
Coincidentally, this shows a general technique for the conversion: turn every non-tail nested call into a flat call by bringing all the nested arguments up to the same level, then implement the needed function semantics according to how it is used and what's expected of it to produce and which laws to follow.
This "do this, and then do that" approach is formalized with the CPS technique, mentioned in the comments:
(define (flatten lst) ;; your definition
(cond
((null? lst) '())
((list? (car lst))
(append
(flatten (car lst))
(flatten (cdr lst))))
(else
(cons (car lst)
(flatten (cdr lst))))))
===
(define (flatten lst) ;; becomes
(flat lst (lambda (x) x)))
(define (flat lst c) ;; continuation "c" = "and then
(cond ;; use the result as"
((null? lst) (c '()))
((list? (car lst))
(flat (cdr lst) (lambda (rd)
(flat (car lst) (lambda (ra)
(c (append ra rd))))))) ;; NB: append (??)
(else
(flat (cdr lst) (lambda (rd)
(c (cons (car lst) rd)))))))
But what's with that pesky append
? We can eliminate it with the
same technique as we used in flaprev
, by bringing all the nested arguments up to the same level, adding an explicit parameter for the list to reverse-append unto:
(define (flatten lst)
(flat lst '() (lambda (x) x)))
;; ^^^__
;; vvv
(define (flat lst d c)
(cond ;; ___vvv
((null? lst) (c d))
((list? (car lst))
(flat (cdr lst) d (lambda (rd)
(flat (car lst) rd (lambda (ra)
(c ra))))))
(else
(flat (cdr lst) d (lambda (rd)
(c (cons (car lst) rd)))))))
Now the length of the longest continuation is that of the longest sublist in the input, instead of the length of the output list.