From 6de6f8494f80dbf21ae762af6698e100cf37e4c5 Mon Sep 17 00:00:00 2001 From: Stefan Kangas Date: Sat, 22 Feb 2025 17:32:31 +0100 Subject: [PATCH] New macros incf and decf * lisp/emacs-lisp/cl-lib.el (cl-incf, cl-decf): Move macros from here... * lisp/emacs-lisp/gv.el (incf, decf): ...to here. Make old names into aliases, documented as deprecated. * lisp/obsolete/cl.el: Don't alias incf and decf. * test/lisp/emacs-lisp/cl-lib-tests.el (cl-lib-test-incf) (cl-lib-test-decf): Move tests from here... * test/lisp/emacs-lisp/gv-tests.el (gv-incf, gv-decf): ...to here. * doc/lispref/numbers.texi (Arithmetic Operations): * lisp/emacs-lisp/shortdoc.el (number): Document incf and decf. * doc/lispref/variables.texi (Multisession Variables): * doc/misc/cl.texi (Organization, Modify Macros, Modify Macros) (Modify Macros, Macro Bindings, For Clauses, Property Lists) (Structures, Efficiency Concerns, Obsolete Setf Customization): Delete cl-incf and cl-decf documentation, moving any relevant parts to lispref. Delete some parts that seem to primarily regard implementation details that do not warrant inclusion in lispref. Update all examples to use incf/decf. (cherry picked from commit 95fee880e45184f4820e9704b75887ef2d91bd01) --- doc/lispref/numbers.texi | 19 ++++- doc/lispref/variables.texi | 2 +- doc/misc/cl.texi | 112 ++++++--------------------- lisp/emacs-lisp/cl-lib.el | 22 +++--- lisp/emacs-lisp/gv.el | 32 +++++--- lisp/emacs-lisp/shortdoc.el | 12 ++- lisp/obsolete/cl.el | 2 - test/lisp/emacs-lisp/cl-lib-tests.el | 36 --------- test/lisp/emacs-lisp/gv-tests.el | 36 +++++++++ 9 files changed, 118 insertions(+), 155 deletions(-) diff --git a/doc/lispref/numbers.texi b/doc/lispref/numbers.texi index 17fa1e05fee..29105959ecd 100644 --- a/doc/lispref/numbers.texi +++ b/doc/lispref/numbers.texi @@ -668,8 +668,8 @@ foo @result{} 4 @end example -If you want to increment the variable, you must use @code{setq}, -like this: +If you want to increment the variable, you must use @code{setq} (or +@code{incf}), like this: @example (setq foo (1+ foo)) @@ -681,6 +681,21 @@ like this: This function returns @var{number-or-marker} minus 1. @end defun +@defmac incf place &optional delta +This macro increments the number stored in @var{place} by one, or +by @var{delta} if specified. The incremented value is returned. + +@var{place} can be a symbol or a generalized variable, @xref{Generalized +Variables}. For example, @code{(incf i)} is equivalent to +@code{(setq i (1+ i))}, and @code{(incf (car x) 2)} is equivalent to +@code{(setcar x (+ (car x) 2))}. +@end defmac + +@defmac decf place &optional delta +This macro decrements the number stored in @var{place} by one, or +by @var{delta} if specified. The decremented value is returned. +@end defmac + @defun + &rest numbers-or-markers This function adds its arguments together. When given no arguments, @code{+} returns 0. diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi index ce7bd27eef6..6f2dbd6b3ec 100644 --- a/doc/lispref/variables.texi +++ b/doc/lispref/variables.texi @@ -3130,7 +3130,7 @@ If the multisession variable is synchronized, setting it may update the value first. For instance: @lisp -(cl-incf (multisession-value foo-bar)) +(incf (multisession-value foo-bar)) @end lisp This first checks whether the value has changed in a different diff --git a/doc/misc/cl.texi b/doc/misc/cl.texi index f95900a085e..8fb308e64a5 100644 --- a/doc/misc/cl.texi +++ b/doc/misc/cl.texi @@ -168,9 +168,6 @@ and information about the package. This file is relatively compact. @item cl-extra.el This file contains the larger, more complex or unusual functions. -It is kept separate so that packages which only want to use Common -Lisp fundamentals like the @code{cl-incf} function won't need to pay -the overhead of loading the more advanced functions. @item cl-seq.el This file contains most of the advanced functions for operating @@ -197,8 +194,8 @@ this package prior to Emacs 24.3. Nowadays, it is replaced by but use different function names (in fact, @file{cl.el} mainly just defines aliases to the @file{cl-lib.el} definitions). Where @file{cl-lib.el} defines a function called, for example, -@code{cl-incf}, @file{cl.el} uses the same name but without the -@samp{cl-} prefix, e.g., @code{incf} in this example. There are a few +@code{cl-first}, @file{cl.el} uses the same name but without the +@samp{cl-} prefix, e.g., @code{first} in this example. There are a few exceptions to this. First, functions such as @code{cl-defun} where the unprefixed version was already used for a standard Emacs Lisp function. In such cases, the @file{cl.el} version adds a @samp{*} @@ -1028,7 +1025,7 @@ generalized variables. @menu * Setf Extensions:: Additional @code{setf} places. -* Modify Macros:: @code{cl-incf}, @code{cl-rotatef}, @code{cl-letf}, @code{cl-callf}, etc. +* Modify Macros:: @code{cl-rotatef}, @code{cl-letf}, @code{cl-callf}, etc. @end menu @node Setf Extensions @@ -1085,52 +1082,6 @@ Specifically, all subforms are evaluated from left to right, then all the assignments are done (in an undefined order). @end defmac -@defmac cl-incf place &optional x -This macro increments the number stored in @var{place} by one, or -by @var{x} if specified. The incremented value is returned. For -example, @code{(cl-incf i)} is equivalent to @code{(setq i (1+ i))}, and -@code{(cl-incf (car x) 2)} is equivalent to @code{(setcar x (+ (car x) 2))}. - -As with @code{setf}, care is taken to preserve the ``apparent'' order -of evaluation. For example, - -@example -(cl-incf (aref vec (cl-incf i))) -@end example - -@noindent -appears to increment @code{i} once, then increment the element of -@code{vec} addressed by @code{i}; this is indeed exactly what it -does, which means the above form is @emph{not} equivalent to the -``obvious'' expansion, - -@example -(setf (aref vec (cl-incf i)) - (1+ (aref vec (cl-incf i)))) ; wrong! -@end example - -@noindent -but rather to something more like - -@example -(let ((temp (cl-incf i))) - (setf (aref vec temp) (1+ (aref vec temp)))) -@end example - -@noindent -Again, all of this is taken care of automatically by @code{cl-incf} and -the other generalized-variable macros. - -As a more Emacs-specific example of @code{cl-incf}, the expression -@code{(cl-incf (point) @var{n})} is essentially equivalent to -@code{(forward-char @var{n})}. -@end defmac - -@defmac cl-decf place &optional x -This macro decrements the number stored in @var{place} by one, or -by @var{x} if specified. -@end defmac - @defmac cl-pushnew x place @t{&key :test :test-not :key} This macro inserts @var{x} at the front of the list stored in @var{place}, but only if @var{x} isn't present in the list already. @@ -1243,8 +1194,8 @@ It does the bindings in sequential rather than parallel order. This is the ``generic'' modify macro. It calls @var{function}, which should be an unquoted function name, macro name, or lambda. It passes @var{place} and @var{args} as arguments, and assigns the -result back to @var{place}. For example, @code{(cl-incf @var{place} -@var{n})} is the same as @code{(cl-callf + @var{place} @var{n})}. +result back to @var{place}. For example, @code{(incf @var{place} +@var{n})} could be implemented as @code{(cl-callf + @var{place} @var{n})}. Some more examples: @example @@ -1264,7 +1215,7 @@ equivalent to @code{(cl-callf2 cons @var{x} @var{place})}. @end defmac The @code{cl-callf} and @code{cl-callf2} macros serve as building -blocks for other macros like @code{cl-incf}, and @code{cl-pushnew}. +blocks for other macros like @code{cl-pushnew}. The @code{cl-letf} and @code{cl-letf*} macros are used in the processing of symbol macros; @pxref{Macro Bindings}. @@ -1401,7 +1352,7 @@ replaced by @var{expansion}. @example (setq bar '(5 . 9)) (cl-symbol-macrolet ((foo (car bar))) - (cl-incf foo)) + (incf foo)) bar @result{} (6 . 9) @end example @@ -1426,7 +1377,7 @@ expansion of another macro: body)))) (setq mylist '(1 2 3 4)) -(my-dolist (x mylist) (cl-incf x)) +(my-dolist (x mylist) (incf x)) mylist @result{} (2 3 4 5) @end example @@ -1440,14 +1391,14 @@ shown here expands to @example (cl-loop for G1234 on mylist do (cl-symbol-macrolet ((x (car G1234))) - (cl-incf x))) + (incf x))) @end example @noindent which in turn expands to @example -(cl-loop for G1234 on mylist do (cl-incf (car G1234))) +(cl-loop for G1234 on mylist do (incf (car G1234))) @end example @xref{Loop Facility}, for a description of the @code{cl-loop} macro. @@ -1999,7 +1950,7 @@ a @code{setf}-able ``reference'' onto the elements of the list rather than just a temporary variable. For example, @example -(cl-loop for x in-ref my-list do (cl-incf x)) +(cl-loop for x in-ref my-list do (incf x)) @end example @noindent @@ -2940,7 +2891,7 @@ The @code{get} and @code{cl-get} functions are also @code{setf}-able. The fact that @code{default} is ignored can sometimes be useful: @example -(cl-incf (cl-get 'foo 'usage-count 0)) +(incf (cl-get 'foo 'usage-count 0)) @end example Here, symbol @code{foo}'s @code{usage-count} property is incremented @@ -4051,7 +4002,7 @@ calling @code{(person-first-name @var{p})}, @code{(person-age slots by using @code{setf} on any of these place forms, for example: @example -(cl-incf (person-age birthday-boy)) +(incf (person-age birthday-boy)) @end example You can create a new @code{person} by calling @code{make-person}, @@ -4459,29 +4410,14 @@ user to modify @var{place}. Many of the advanced features of this package, such as @code{cl-defun}, @code{cl-loop}, etc., are implemented as Lisp macros. In byte-compiled code, these complex notations will be expanded into -equivalent Lisp code which is simple and efficient. For example, -the form - -@example -(cl-incf i n) -@end example - -@noindent -is expanded at compile-time to the Lisp form - -@example -(setq i (+ i n)) -@end example - -@noindent -which is the most efficient way of doing this operation -in Lisp. Thus, there is no performance penalty for using the more -readable @code{cl-incf} form in your compiled code. +equivalent Lisp code which is simple and efficient. Thus, there is no +performance penalty for using the more readable form in your compiled +code. @emph{Interpreted} code, on the other hand, must expand these macros every time they are executed. For this reason it is strongly recommended that code making heavy use of macros be compiled. -A loop using @code{cl-incf} a hundred times will execute considerably +A loop using @code{cl-first} a hundred times will execute considerably faster if compiled, and will also garbage-collect less because the macro expansion will not have to be generated, used, and thrown away a hundred times. @@ -4907,7 +4843,7 @@ call to @code{make-adder} itself. @example (defun make-counter () (lexical-let ((n 0)) - (cl-function (lambda (&optional (m 1)) (cl-incf n m))))) + (cl-function (lambda (&optional (m 1)) (incf n m))))) (setq count-1 (make-counter)) (funcall count-1 3) @result{} 3 @@ -5052,7 +4988,7 @@ In Emacs, these are obsolete, replaced by various features of @defmac define-modify-macro name arglist function [doc-string] This macro defines a ``read-modify-write'' macro similar to -@code{cl-incf} and @code{cl-decf}. You can replace this macro +@code{incf} and @code{decf}. You can replace this macro with @code{gv-letplace}. The macro @var{name} is defined to take a @var{place} argument @@ -5180,8 +5116,8 @@ For example, the simple form of @code{defsetf} is shorthand for The Lisp form that is returned can access the arguments from @var{arglist} and @var{store-var} in an unrestricted fashion; -macros like @code{cl-incf} that invoke this -setf-method will insert temporary variables as needed to make +macros that invoke this +setf-method should insert temporary variables as needed to make sure the apparent order of evaluation is preserved. Another standard example: @@ -5245,11 +5181,7 @@ temporary variables. In the setf-methods generated by @code{defsetf}, the second return value is simply the list of arguments in the place form, and the first return value is a list of a corresponding number of temporary variables generated -@c FIXME I don't think this is true anymore. -by @code{cl-gensym}. Macros like @code{cl-incf} that -use this setf-method will optimize away most temporaries that -turn out to be unnecessary, so there is little reason for the -setf-method itself to optimize. +by @code{cl-gensym}. @end defmac @node GNU Free Documentation License diff --git a/lisp/emacs-lisp/cl-lib.el b/lisp/emacs-lisp/cl-lib.el index e9f1c02e4ab..2ab2f9f9022 100644 --- a/lisp/emacs-lisp/cl-lib.el +++ b/lisp/emacs-lisp/cl-lib.el @@ -105,29 +105,27 @@ a future Emacs interpreter will be able to use it.") ;; can safely be used in init files. ;;;###autoload -(defmacro cl-incf (place &optional x) +(defalias 'cl-incf #'incf "Increment PLACE by X (1 by default). PLACE may be a symbol, or any generalized variable allowed by `setf'. The return value is the incremented value of PLACE. If X is specified, it should be an expression that should -evaluate to a number." - (declare (debug (place &optional form))) - (if (symbolp place) - (list 'setq place (if x (list '+ place x) (list '1+ place))) - (list 'cl-callf '+ place (or x 1)))) +evaluate to a number. + +This macro is considered deprecated in favor of the built-in macro +`incf' that was added in Emacs 31.1.") -(defmacro cl-decf (place &optional x) +(defalias 'cl-decf #'decf "Decrement PLACE by X (1 by default). PLACE may be a symbol, or any generalized variable allowed by `setf'. The return value is the decremented value of PLACE. If X is specified, it should be an expression that should -evaluate to a number." - (declare (debug cl-incf)) - (if (symbolp place) - (list 'setq place (if x (list '- place x) (list '1- place))) - (list 'cl-callf '- place (or x 1)))) +evaluate to a number. + +This macro is considered deprecated in favor of the built-in macro +`decf' that was added in Emacs 31.1.") (defmacro cl-pushnew (x place &rest keys) "Add X to the list stored in PLACE unless X is already in the list. diff --git a/lisp/emacs-lisp/gv.el b/lisp/emacs-lisp/gv.el index 5ec21195ab7..84b301d2b0a 100644 --- a/lisp/emacs-lisp/gv.el +++ b/lisp/emacs-lisp/gv.el @@ -315,17 +315,29 @@ The return value is the last VAL in the list. ;; `(if (member ,v ,getter) nil ;; ,(funcall setter `(cons ,v ,getter)))))) -;; (defmacro gv-inc! (place &optional val) -;; "Increment PLACE by VAL (default to 1)." -;; (declare (debug (gv-place &optional form))) -;; (gv-letplace (getter setter) place -;; (funcall setter `(+ ,getter ,(or val 1))))) +;;;###autoload +(defmacro incf (place &optional delta) + "Increment PLACE by DELTA (default to 1). -;; (defmacro gv-dec! (place &optional val) -;; "Decrement PLACE by VAL (default to 1)." -;; (declare (debug (gv-place &optional form))) -;; (gv-letplace (getter setter) place -;; (funcall setter `(- ,getter ,(or val 1))))) +The DELTA is first added to PLACE, and then stored in PLACE. +Return the incremented value of PLACE. + +See also `decf'." + (declare (debug (gv-place &optional form))) + (gv-letplace (getter setter) place + (funcall setter `(+ ,getter ,(or delta 1))))) + +;;;###autoload +(defmacro decf (place &optional delta) + "Decrement PLACE by DELTA (default to 1). + +The DELTA is first subtracted from PLACE, and then stored in PLACE. +Return the decremented value of PLACE. + +See also `incf'." + (declare (debug (gv-place &optional form))) + (gv-letplace (getter setter) place + (funcall setter `(- ,getter ,(or delta 1))))) ;; For Edebug, the idea is to let Edebug instrument gv-places just like it does ;; for normal expressions, and then give it a gv-expander to DTRT. diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index 23b9b582a9a..77a4ec4f21c 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el @@ -1375,9 +1375,17 @@ A FUNC form can have any number of `:no-eval' (or `:no-value'), :eval (mod 10 6) :eval (mod 10.5 6)) (1+ - :eval (1+ 2)) + :eval (1+ 2) + :eval (let ((x 2)) (1+ x) x)) (1- - :eval (1- 4)) + :eval (1- 4) + :eval (let ((x 4)) (1- x) x)) + (incf + :eval (let ((x 2)) (incf x) x) + :eval (let ((x 2)) (incf x 2) x)) + (decf + :eval (let ((x 4)) (decf x) x) + :eval (let ((x 4)) (decf x 2)) x) "Predicates" (= :args (number &rest numbers) diff --git a/lisp/obsolete/cl.el b/lisp/obsolete/cl.el index 5baa155c592..5fbfbb7899e 100644 --- a/lisp/obsolete/cl.el +++ b/lisp/obsolete/cl.el @@ -282,8 +282,6 @@ values-list values pushnew - decf - incf )) (let ((new (if (consp fun) (prog1 (cdr fun) (setq fun (car fun))) (intern (format "cl-%s" fun))))) diff --git a/test/lisp/emacs-lisp/cl-lib-tests.el b/test/lisp/emacs-lisp/cl-lib-tests.el index 376566958a0..d7c38b73432 100644 --- a/test/lisp/emacs-lisp/cl-lib-tests.el +++ b/test/lisp/emacs-lisp/cl-lib-tests.el @@ -63,42 +63,6 @@ (should (equal (cl-multiple-value-list nil) nil)) (should (equal (cl-multiple-value-list (list 1 2 3)) '(1 2 3)))) -(defvar cl-lib-test--special 0) - -(ert-deftest cl-lib-test-incf () - (setq cl-lib-test--special 0) - (should (= (cl-incf cl-lib-test--special) 1)) - (should (= cl-lib-test--special 1)) - (should (= (cl-incf cl-lib-test--special 9) 10)) - (should (= cl-lib-test--special 10)) - (let ((var 0)) - (should (= (cl-incf var) 1)) - (should (= var 1)) - (should (= (cl-incf var 9) 10)) - (should (= var 10))) - (let ((alist)) - (should (= (cl-incf (alist-get 'a alist 0)) 1)) - (should (= (alist-get 'a alist 0) 1)) - (should (= (cl-incf (alist-get 'a alist 0) 9) 10)) - (should (= (alist-get 'a alist 0) 10)))) - -(ert-deftest cl-lib-test-decf () - (setq cl-lib-test--special 0) - (should (= (cl-decf cl-lib-test--special) -1)) - (should (= cl-lib-test--special -1)) - (should (= (cl-decf cl-lib-test--special 9) -10)) - (should (= cl-lib-test--special -10)) - (let ((var 1)) - (should (= (cl-decf var) 0)) - (should (= var 0)) - (should (= (cl-decf var 10) -10)) - (should (= var -10))) - (let ((alist)) - (should (= (cl-decf (alist-get 'a alist 0)) -1)) - (should (= (alist-get 'a alist 0) -1)) - (should (= (cl-decf (alist-get 'a alist 0) 9) -10)) - (should (= (alist-get 'a alist 0) -10)))) - (ert-deftest cl-digit-char-p () (should (eql 3 (cl-digit-char-p ?3))) (should (eql 10 (cl-digit-char-p ?a 11))) diff --git a/test/lisp/emacs-lisp/gv-tests.el b/test/lisp/emacs-lisp/gv-tests.el index 5ea386e0b5d..892af4bfab1 100644 --- a/test/lisp/emacs-lisp/gv-tests.el +++ b/test/lisp/emacs-lisp/gv-tests.el @@ -163,6 +163,42 @@ its getter (Bug#41853)." (eval-buffer)))) (should (equal (get 'gv-setter-edebug 'gv-setter-edebug-prop) '(123)))) +(defvar gv-test--special 0) + +(ert-deftest gv-incf () + (setq gv-test--special 0) + (should (= (incf gv-test--special) 1)) + (should (= gv-test--special 1)) + (should (= (incf gv-test--special 9) 10)) + (should (= gv-test--special 10)) + (let ((var 0)) + (should (= (incf var) 1)) + (should (= var 1)) + (should (= (incf var 9) 10)) + (should (= var 10))) + (let ((alist)) + (should (= (incf (alist-get 'a alist 0)) 1)) + (should (= (alist-get 'a alist 0) 1)) + (should (= (incf (alist-get 'a alist 0) 9) 10)) + (should (= (alist-get 'a alist 0) 10)))) + +(ert-deftest gv-decf () + (setq gv-test--special 0) + (should (= (decf gv-test--special) -1)) + (should (= gv-test--special -1)) + (should (= (decf gv-test--special 9) -10)) + (should (= gv-test--special -10)) + (let ((var 1)) + (should (= (decf var) 0)) + (should (= var 0)) + (should (= (decf var 10) -10)) + (should (= var -10))) + (let ((alist)) + (should (= (decf (alist-get 'a alist 0)) -1)) + (should (= (alist-get 'a alist 0) -1)) + (should (= (decf (alist-get 'a alist 0) 9) -10)) + (should (= (alist-get 'a alist 0) -10)))) + (ert-deftest gv-plist-get () ;; Simple `setf' usage for `plist-get'. (let ((target (list :a "a" :b "b" :c "c"))) -- 2.39.5