
;;;; Different ways to write a recursive shallow copy of a list.
;;;;  Recall that Elisp does not perform TCO, so there is really
;;;;    no tangible performance benefit from the tail-calls---
;;;;    but SBCL should show one!

;;;; Project idea: write a bytecode-level profiler for Elisp,
;;;;   capable of precisely counting the cons cells created by a function.

;;; Basic recursive (not tail-recursive) shallow copy of a list
(defun copy-list (x)
  (cond ((null x) nil)
        ((consp x)
         (cons (car x) (copy-list (cdr x))))))  ; not tail-recursive, due to cons needing
                                                ;  to be called after copy-list call.

;;; Tail-recursive, with accumulator and append.
;;;   Note: append makes it ver suboptimal.
(defun copy-list (x acc)
  (cond ((null x) acc)    ; nothing left to do, return accumulated result
        ((consp x)
         (copy-list (cdr x) (append acc 
                                    (cons (car x) nil)))))) ; why this cons? see below

;; To see the trace: (trace-function 'copy-list)

;;-------------------[ Regarding append ]-------------------
;; Recall, to get a list, you need to pass a list to append as a second 
;;   argument. Otherwise, you get a list ending in a dotted pair:
(append '(1 23 ) 'a)
(1 23 . a)

;; Also, recall this:
(append nil 'a)
a
(nconc nil 'a)
a
(nconc nil '(a))
(a)
;;-----------------------------------------------------------

;; The above is very wasteful: append must traverse the list 
;;   every time _and_ makes shallow copies of its arguments!
;;
;; This version is a bit better: it does not create extra
;;  cons cells, just the one needed per element for the shallow copy.
;;  However, nconc still traverses the list every time.
(defun copy-list (x acc)
  (cond ((null x) acc)
        ((consp x)
         (copy-list (cdr x) (nconc acc
                                   (cons (car x) nil))))))

;; This should be more more efficient: only one cons cell created per element,
;;   _prepended_ to the accumulator list (to save traversing to its end),
;;   and then only one travesal at the end, to reverse the accumulator list.
(defun copy-list (x acc)
  (cond ((null x) (nreverse acc))
        ((consp x)
         (copy-list (cdr x) (cons (car x) acc)))))

;;; What if we don't want the NREVERSE? The problem is that if we
;;;   want to append, not _prepend_ the new cons cells, we need 
;;;   to modify the cons cell created during the _previous_ recursive
;;;   call, pushing its modification into the current call. 
;;; This technique can be generalized to any simple data construction operations
;;;   that need to occur with the _results_ of the recursive call (which
;;;   would break straightforward tail recursion); see
;;;   https://en.wikipedia.org/wiki/Tail_call#Tail_recursion_modulo_cons

;;; Here it is for shallow list copy:
(defun copy-list (l)
  (let ((seed-cell (cons 'dummy nil)))
    (labels ((tc (x cell)
                 ;; this is the cell to attach our new one to
                 (cond ((null x) nil)  ; doesn't matter. This should be an IF
                       ((consp x)
                        (let ((new-cell (cons (car x) nil)))
                          (rplacd cell new-cell)     ; destructive on cell from previous call!
                          (tc (cdr x) new-cell))))))
      (tc l seed-cell)    ; tc's return value isn't relevant; we're after its side-effect on seed-cell
      (cdr seed-cell))))

;;;; In reality, there's barely a difference between the three.
;;;;  Likely, this is a result of garbage collection:

;; append version:
"copy-list      600000      9.4691200000  1.578...e-05"
"copy-list      600000      10.140059000  1.690...e-05"
"copy-list      600000      3.1072689999  5.178...e-06" ; <--??? why? Garbage collection skipped?
"copy-list      600000      13.173576000  2.195...e-05"
"copy-list      600000      11.184488000  1.864...e-05"

;; nconc version:
"copy-list      600000      9.9489790000  1.658...e-05"
"copy-list      600000      9.3005690000  1.550...e-05"
"copy-list      600000      9.7946060000  1.632...e-05"

;; nreverse+cons version:
"copy-list      600000      9.6623510000  1.610...e-05"
"copy-list      600000      10.688309000  1.781...e-05"
"copy-list      600000      10.430074000  1.738...e-05"
"copy-list      600000      9.9335370000  1.655...e-05"
"copy-list      600000      9.2891800000  1.548...e-05"
"copy-list      600000      10.448907000  1.741...e-05"

;; modulo-cons version:
"copy-list      100000      1.5461220000  1.546...e-05"
"copy-list      100000      1.1891990000  1.189...e-05"
"copy-list      100000      0.5176339999  5.176...e-06"  ; <-- ?? 
"copy-list      100000      1.5362150000  1.536...e-05"
"copy-list      100000      1.4385760000  1.438...e-05"
"copy-list      100000      1.5139790000  1.513...e-05"

;;;
;;;  Profiling script for the above. elp-results pops up its result buffer,
;;;    but I wanted just the plain string of results. Hence save-window-excursion
;;;    saves and restores the window configuration, then I grab the contents
;;;    of the results buffer, and strip the ending newline   
;;; 
(defun profile-here ()
  ;; instrument or re-instrument function with ELP library
  (elp-instrument-function 'copy-list)
  (dotimes (i 100000)
    (copy-list '(a b c d e))) ; nil))
  ;; grab results from the specially named buffer
  ;; (setq elp-use-standard-output t) -- this doesn't really work, extra buffer still gets shown!
  (save-window-excursion
    (elp-results))
  (with-current-buffer (get-buffer "*ELP Profiling Results*")
    ;; is this replace really needed? Just grab from (point-min) to (1- (point-max))?
    (replace-regexp-in-string "\n\\'" ; replace \n anchored at the end of string \' 
                              ""      ;  ...with empty string
                              (buffer-substring-no-properties (point-min) (point-max)))))

;; To run a profiling round:
(profile-here)

;; Something about how the ELP profiler works:
;;   https://www.emacswiki.org/emacs/EmacsLispProfiler
(pp (symbol-function 'copy-list))
;; #[128 "\300\301\302#\207"
;;       [apply
;;        #[385 "	\203\f \300	=\203\f \304\300N\305\204 \306\307\300\"\210\n\204& \310\"\262\202V \305\211\211\311\311HTI\266\312 \262\310\"\262\312 \262\211\313\313H\314\315\"!\266\202\\I\266	\203b \300	=\203b \305\207"
;;              [copy-list elp-master elp-record-p elp-timer-info-property t nil error "%s is not instrumented for profiling" apply 0 current-time 1 float-time time-subtract]
;;              16 "This function has been instrumented for profiling by the ELP.\nELP is the Emacs Lisp Profiler.  To restore the function to its\noriginal definition, use \\[elp-restore-function] or \\[elp-restore-all].\n\n(fn FUNC &rest ARGS)"]
;;        (lambda
;;          (x acc)
;;          (cond
;;           ((null x)
;;            acc)
;;           ((consp x)
;;            (copy-list
;;             (cdr x)
;;             (append acc
;;                     (cons
;;                      (car x)
;;                      nil))))))
;;        ((name . ELP-instrumentation\ )
;;         (depth . -99))]
;;       5 nil]
;; "#[128 \"\\300\\301\\302#\\207\"
;;       [apply
;;        #[385 \"	\\203\\f \\300	=\\203\\f \\304\\300N\\305\\204 \\306\\307\\300\\\"\\210\\n\\204& \\310\\\"\\262\\202V \\305\\211\\211\\311\\311HTI\\266\\312 \\262\\310\\\"\\262\\312 \\262\\211\\313\\313H\\314\\315\\\"!\\266\\202\\\\I\\266	\\203b \\300	=\\203b \\305\\207\"
;;              [copy-list elp-master elp-record-p elp-timer-info-property t nil error \"%s is not instrumented for profiling\" apply 0 current-time 1 float-time time-subtract]
;;              16 \"This function has been instrumented for profiling by the ELP.\\nELP is the Emacs Lisp Profiler.  To restore the function to its\\noriginal definition, use \\\\[elp-restore-function] or \\\\[elp-restore-all].\\n\\n(fn FUNC &rest ARGS)\"]
;;        (lambda
;;          (x acc)
;;          (cond
;;           ((null x)
;;            acc)
;;           ((consp x)
;;            (copy-list
;;             (cdr x)
;;             (append acc
;;                     (cons
;;                      (car x)
;;                      nil))))))
;;        ((name . ELP-instrumentation\\ )
;;         (depth . -99))]
;;       5 nil]
;; "


(setq l '(a b (c) d))
(a b (c) d)

(copy-list-tc '(a b (c) d))
(a b (c) d)

(eq (caddr l) (caddr (copy-list-tc l)))
t




    
                              

    
