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?