https://sep.yimg.com/ty/cdn/paulgraham/onlisp.pdf On page 36 Paul introduces the following function:
(defun flatten (x)
(labels ((rec (x acc)
(cond ((null x) acc)
((atom x) (cons x acc))
(t (rec (car x) (rec (cdr x) acc))))))
(rec x nil)))
I'm trying to gain an intuition as to how he came to this implementation. I know that recursion is a big theme in Emacs Lisp (and Lisp in general), but trying to work through some of this code gives me a headache. Is there a strategy for studying code like this? Or does there need to be a paradigm shift in the way I'm reasoning about code such as this? Or an external resource that could give me a better intuition? Or, over time, will these idioms become more apparent to me (if this is in fact idiomatic Lisp)?
For example, given this input to function flatten:
(flatten '(1 (2 3 (4 5))))
This is the trace of the given output:
{ flatten args: ((1 (2 3 (4 5))))
:{ rec@cl-flet@381 args: ((1 (2 3 (4 5))) nil)
::{ rec@cl-flet@381 args: (((2 3 (4 5))) nil)
:::{ rec@cl-flet@381 args: (nil nil)
:::} rec@cl-flet@381 result: nil
:::{ rec@cl-flet@381 args: ((2 3 (4 5)) nil)
::::{ rec@cl-flet@381 args: ((3 (4 5)) nil)
:::::{ rec@cl-flet@381 args: (((4 5)) nil)
::::::{ rec@cl-flet@381 args: (nil nil)
::::::} rec@cl-flet@381 result: nil
::::::{ rec@cl-flet@381 args: ((4 5) nil)
:::::::{ rec@cl-flet@381 args: ((5) nil)
::::::::{ rec@cl-flet@381 args: (nil nil)
::::::::} rec@cl-flet@381 result: nil
::::::::{ rec@cl-flet@381 args: (5 nil)
::::::::} rec@cl-flet@381 result: (5)
:::::::} rec@cl-flet@381 result: (5)
:::::::{ rec@cl-flet@381 args: (4 (5))
:::::::} rec@cl-flet@381 result: (4 5)
::::::} rec@cl-flet@381 result: (4 5)
:::::} rec@cl-flet@381 result: (4 5)
:::::{ rec@cl-flet@381 args: (3 (4 5))
:::::} rec@cl-flet@381 result: (3 4 5)
::::} rec@cl-flet@381 result: (3 4 5)
::::{ rec@cl-flet@381 args: (2 (3 4 5))
::::} rec@cl-flet@381 result: (2 3 4 5)
:::} rec@cl-flet@381 result: (2 3 4 5)
::} rec@cl-flet@381 result: (2 3 4 5)
::{ rec@cl-flet@381 args: (1 (2 3 4 5))
::} rec@cl-flet@381 result: (1 2 3 4 5)
:} rec@cl-flet@381 result: (1 2 3 4 5)
} flatten result: (1 2 3 4 5)
What seems to be trivial input ends up becoming a web of recursive calls. So when you think about functions such as these, do you, in your head, reason from certain base-cases and proceed? Like, for example, would you consider the (nil) case first, then the (1) case, then (1 2), then (1 (2 3)) and so on? Or is that not necessary?