Sujet : Tail call recursion macro with named-let in Emacs Lisp (was: A simple lisp problem.)
De : mail (at) *nospam* axel-reichert.de (Axel Reichert)
Groupes : comp.lang.lispDate : 15. Jun 2024, 12:17:42
Autres entêtes
Organisation : A noiseless patient Spider
Message-ID : <87o782h5mh.fsf@axel-reichert.de>
References : 1
User-Agent : Gnus/5.13 (Gnus v5.13) Emacs/28.2 (gnu/linux)
"B. Pym" <
No_spamming@noWhere_7073.org> writes:
Frank Schwieterman wrote:
>
(defun difference (param)
(if (> (length param) 1)
(if (<= (abs (- (first param) (first (rest param)))) 1 )
(difference (rest param))
nil
)
T
)
)
>
Gauche Scheme
>
(define (diff1? xs)
(every
(lambda (a b) (= 1 (abs (- a b))))
xs
(cdr xs)))
>
(diff1? '(2 3 4 3 4 5 6 7 8 9 8))
===>
#t
(diff1? '(2 3 4 3 4 5 6 7 8 9 8 0))
===>
#f
A nice, recursive solution in Emacs Lisp would be
(defun single-step-p (xs)
(cond ((null (cdr xs)) t)
((= 1 (abs (- (car xs) (cadr xs)))) (single-step-p (cdr xs)))
(t nil)))
but it fails for longer lists such as
(single-step-p (number-sequence 1 1000))
because Emacs Lisp does not have proper tail call elimination. There is
an exception, though, "named-let", see here:
https://www.gnu.org/software/emacs/manual/html_node/elisp/Local-Variables.htmlSo I rewrote the function to
(defun single-step-p (xs)
(named-let single-step-p ((xs xs))
(cond ((null (cdr xs)) t)
((= 1 (abs (- (car xs) (cadr xs)))) (single-step-p (cdr xs)))
(t nil))))
which work fine also for
(single-step-p (number-sequence 1 1000000))
but of course looks a bit redundant compared to the "normal" use of
"named-let" for helper functions such as accumulating results etc.
So I wrote a small macro doing the wrapping in a "named-let" for me:
(defmacro defun-tco-1 (name args body)
`(defun ,name ,args
(named-let ,name (,args ,args)
,body)))
Now
(defun-tco-1 single-step-p (xs)
(cond ((null (cdr xs)) t)
((= 1 (abs (- (car xs) (cadr xs)))) (single-step-p (cdr xs)))
(t nil)))
works, but the macro of course fails for a different number of
arguments, as can be seen with
(macroexpand-1 '(defun-tco-1 foo (a b) (+ a b)))
which gives
(defun foo (a b) (named-let foo ((a b) (a b)) (+ a b)))
and of course also wrong results (14 instead of 10) for
(foo 3 7)
So I needed to loop through the args of the macro and construct the
proper bindings for the "named-let":
(defmacro defun-tco (name args body)
(let ((bindings (mapcar #'(lambda (arg) (list arg arg)) args)))
`(defun ,name ,args
(named-let ,name ,bindings
,body))))
Is this fine from the point of the experts here (in fact, it is my first
macro that is unrelated to any macro tutorials/book, but rather came
from my own needs)? I am quite sure that I could suffer from variable
capture and perhaps also from multiple evaluation, but this would be the
second step for me after a first, general criticism here.
Many thank for any comments!
Best regards
Axel