Subject: Re: bug#72344: [PATCH] Add a version of cl-once-only which handles lists of forms

From: Thuna <[email-masked]>
Date: Tue, 13 Aug 2024 23:17:41 +0200

>> If `cl-once-only-multiple' is fine by you then I would prefer that over
>> `cl-once-only-rest' and `cl-once-only-mult'.  It would help to get more
>> suggestions, but I don't think anyone else will be chiming in at this
>> point.
>
> It's just really long :)

I do agree that it's decently long, but it's probably fine...?
`cl-destructuring-bind' is the same length and I don't find that too
annoying to write out, though maybe that's just me.

> Let's give ourselves time to think of something shorter.

I don't have any new ideas for the name, but here's a second draft which
tries to adapt the example used in `cl-once-only'.  I was unable to get
the `mapcar' in the first example any more concise, so some thoughts on
rewording and fixing various parts of the text would be nice.

If Drew wants to start a new conversation to move `cl-once-only' (and
also this) out of cl-lib we can also just go with any name now and then
finalize it afterwards, though of all the names the one I favor is still
`cl-once-only-multiple' above all others (sans `cl-once-only*' which is
not an option - unless if you changed your mind about it?).

diff --git a/doc/misc/cl.texi b/doc/misc/cl.texi
index 57e2f3a6c3b..de9f0565d03 100644
--- a/doc/misc/cl.texi
+++ b/doc/misc/cl.texi
@@ -2684,10 +2684,10 @@ Macro-Writing Macros
 @end defmac
 
 @defmac cl-once-only* (variable forms) body
-This macro is a version of @code{cl-once-only} which takes a list of
-forms.  This macro is primarily meant to be used where the number of
-forms is unknown and thus @code{cl-once-only} cannot work, such as those
-obtained by a @code{&body} argument.
+This macro is a version of @code{cl-once-only} which takes an
+arbitrarily long list of forms.  This macro is primarily meant to be
+used where the number of forms is unknown and thus @code{cl-once-only}
+cannot work, such as those obtained by a @code{&body} argument.
 
 Each element of @var{variable} may be used to refer to the result of
 evaluating the corresponding form in @var{forms} within @var{body}.
@@ -2696,38 +2696,58 @@ Macro-Writing Macros
 such that each form is evaluated in order and its result is bound to the
 corresponding symbol.
 
-Like @code{cl-once-only}, the first argument can be a symbol
-@var{variable}, which is equivalent to writing @code{(variable
-variable)}.
+Like @code{cl-once-only}, the first argument can be a symbol @var{variable}, which
+is equivalent to writing @code{(variable variable)}.
 
 Consider the following macro:
 
 @example
-(defmacro my-list (head &rest args)
-  (cl-once-only ((args `(list ,@@args))
-    `(list (apply #',head ,args)
-           ,args
-           (nth 1 ,args))))
+(defmacro my-list (vals &rest forms)
+  (let ((val-results (mapcar (lambda (_) (gensym)) vals)))
+    `(let* ,(cl-mapcar #'list val-results vals)
+       (list ,(cl-first val-results)
+             ,(cl-second val-results)
+             ,@@val-results
+             (progn ,@@forms)))))
 @end example
 
-This macro is such that it will evaluate @var{args} only once, however
-that @var{args} was a list is lost once we are in @code{cl-once-only}.
-Furthermore, to access any specific element of @var{args} we must obtain
-the element during evaluation via @code{(nth N ,args)}.
+In a call like @code{(my-list ((pop foo) (cl-incf bar) ...) ...)} the
+@code{pop} and @code{cl-incf} will be evaluated exactly once, ensuring
+their side effects are not applied twice.  This code is however very
+complex, in the same way code not using @code{cl-once-only} is.
 
-Consider the alternative using @code{cl-once-only*}:
+Using @code{cl-once-only} is not possible directly due to it expecting
+individual forms which can be evaluated.  This can be worked around by
+assigning to a variable @code{`(list ,@@vars)} which @emph{can} be
+evaluated:
 
 @example
-(defmacro my-list (head &rest args)
-  (cl-once-only* args
-    `(list (,head ,@@args)
-           (list ,@@args)
-           ,(nth 1 args))))
+(defmacro my-list (vals &rest forms)
+  (cl-once-only ((vals `(list ,@@vals)))
+    `(list (cl-first ,vals)
+           (cl-second ,vals)
+           ,vals                        ; Does not splice
+           (progn ,@@forms))))
+@end example
+
+There are two problems which both result from the fact that @code{vals}
+is not a list inside the body of @code{cl-once-only}: 1. @code{vals}
+cannot be spliced in the way it can in the previous example and
+2. accessing individual elements of @code{vals} can only be done by
+accessing the resulting list @emph{during evaluation}.  Compare this to
+the example using @code{cl-once-only*}:
+
+@example
+(defmacro my-list (vals &rest forms)
+  (cl-once-only* vals
+    `(list ,(cl-first vals)
+           ,(cl-second vals)
+           ,@@vals
+           (progn ,@@forms))))
 @end example
 
-which preserves the fact that @var{args} is a list and allows immediate
-access to individual arguments by simply choosing the corresponding
-element in @var{args}.
+which preserves the fact the @var{vals} is a list and removes
+boiler-plate code for generating and assigning temporary variables.
 @end defmac
 
 @node Macros