;;; ELisp session with disassembly of functions, saved from the *scratch* buffer ;; Note: The describe-bindings command will describe the special key shortcuts that ;; evaluate S-expressions (sexps). We needed eval-print-last-sexp , bound to C-j, ;; so that the evaluation results are inserted into this buffer rather than ;; echoed in the Emacs minibuffer. So Ctrl+j is what I type to eval-and-paste. (+ 1 2) ; pressed Ctrl+j 3 ; output as string (defun plus1 (x) (+ x 1)) plus1 ;; disassembles to a newly created buffer (disassemble 'plus1) nil ;; We want to paste the results into this buffer, so I did "find-function" on disassemble ;; and got its core (disassemble-internal 'plus1 2 nil) doc: ... args: (arg1) 0 dup 1 add1 2 return nil ;; I hate typing so here's a macro to type less (defmacro disas (func) `(disassemble-internal ,func 2 nil)) disas (disas 'plus1) doc: ... args: (arg1) 0 dup 1 add1 2 return nil ;; With that out of the way, let's write our naive recursive length and then tail-recursive one (defun mylen (l) (if (null l) 0 (+ 1 (mylen (cdr l))))) mylen ;; "unit testing" (mylen '(a b c d)) 4 (mylen nil) 0 ;; Let's see if this works with the list of buffers. Emacs's interactive command ;; for showing these in a separate window is list-buffers, the bare list function ;; is buffer-list. (mylen (buffer-list)) 18 ;; Let's check: (buffer-list) (# # # # # # # # # # # # ...) (length (buffer-list)) 18 ;; Note: The above pattern of list-buffers/buffer-list for an interactive command vs ;; core function is followed often but not always. "Emacs has irregular verbs" :) (defun mylen-tco (ll) (named-let mylen-tc ((l ll) ; named-let is a special form of LABELS that allows TCO (acc 0)) (if (null l) acc (mylen-tc (cdr l) (+ 1 acc))))) mylen-tco (mylen-tco '(1 2 3 4 5)) 5 (mylen-tco nil) 0 (number-sequence 1 10) (1 2 3 4 5 6 7 8 9 10) (length (number-sequence 1 5000000)) 5000000 (mylen-tco (number-sequence 1 5000000)) 5000000 ;; By comparison, mylen runs into a stack overflow (mylen (number-sequence 1 5000000)) ;Debugger entered--Lisp error: (excessive-lisp-nesting 1601) ; (if (null l) 0 (+ 1 (mylen (cdr l)))) ; mylen((531 532 533 534 535 536 537 538 539 540 541 542 ;; Suggestion: Figure out at what list length this happens, catch the largest list that is still ;; served by the naive recursive mylen. (disas 'mylen-tco) doc: ... args: (arg1) 0 dup 1 constant 0 2 constant nil 3:1 stack-ref 2 4 stack-ref 2 5 stack-ref 4 6 goto-if-not-nil 2 9 stack-ref 3 10 return 11:2 stack-ref 4 12 cdr 13 stack-set 5 15 stack-ref 3 16 add1 17 stack-set 4 19 discardN 2 21 goto 1 ; <-- no CALL, just a loop, as per TCO nil ;; Suggestion: walk down this bytecode with pen and paper, and convince yourself this works! ;; Use https://rocky.github.io/elisp-bytecode.pdf or AI to look up and understand opcodes.