aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2026-01-09 22:34:32 -0500
committerStefan Monnier2026-01-09 22:34:32 -0500
commit058bac45b2f64dee35e26e22b00bafde41aa5aec (patch)
tree92ed0175c6f37410a5cab2e8af7649fc968fe34a
parent6c818936e00bf24201dbfa8916cd91aca24f84c9 (diff)
downloademacs-058bac45b2f64dee35e26e22b00bafde41aa5aec.tar.gz
emacs-058bac45b2f64dee35e26e22b00bafde41aa5aec.zip
cl-generic.el: Avoid an O(N^2) behavior
When N methods are defined, don't (re)build the dispatch function each time since it takes O(N) time to build it. * lisp/emacs-lisp/cl-generic.el (cl--generic-method): Add docstring. (cl--generic): New `lazy-function` slot. (cl--generic-make-function): Use it and delay building the dispatch function until the next call. [toplevel]: Simplify the bootstrap hacks a bit.
-rw-r--r--lisp/emacs-lisp/cl-generic.el58
1 files changed, 37 insertions, 21 deletions
diff --git a/lisp/emacs-lisp/cl-generic.el b/lisp/emacs-lisp/cl-generic.el
index ea73ce766f5..d501a421ea2 100644
--- a/lisp/emacs-lisp/cl-generic.el
+++ b/lisp/emacs-lisp/cl-generic.el
@@ -154,17 +154,22 @@ also passed as second argument to SPECIALIZERS-FUNCTION."
154 (:constructor cl--generic-make-method 154 (:constructor cl--generic-make-method
155 (specializers qualifiers call-con function)) 155 (specializers qualifiers call-con function))
156 (:predicate nil)) 156 (:predicate nil))
157 "Type of `cl-generic' method objects.
158FUNCTION holds a function containing the actual code of the method.
159SPECIALIZERS holds the list of specializers (as long as the number of
160mandatory arguments of the method).
161QUALIFIERS holds the list of qualifiers.
162CALL-CON indicates the calling convention expected by FUNCTION:
163- nil: FUNCTION is just a normal function with no extra arguments for
164 `call-next-method' or `next-method-p' (which it hence can't use).
165- `curried': FUNCTION is a curried function that first takes the
166 \"next combined method\" and returns the resulting combined method.
167 It can distinguish `next-method-p' by checking if that next method
168 is `cl--generic-isnot-nnm-p'.
169- t: FUNCTION takes the `call-next-method' function as an extra first
170 argument."
157 (specializers nil :read-only t :type list) 171 (specializers nil :read-only t :type list)
158 (qualifiers nil :read-only t :type (list-of atom)) 172 (qualifiers nil :read-only t :type (list-of atom))
159 ;; CALL-CON indicates the calling convention expected by FUNCTION:
160 ;; - nil: FUNCTION is just a normal function with no extra arguments for
161 ;; `call-next-method' or `next-method-p' (which it hence can't use).
162 ;; - `curried': FUNCTION is a curried function that first takes the
163 ;; "next combined method" and return the resulting combined method.
164 ;; It can distinguish `next-method-p' by checking if that next method
165 ;; is `cl--generic-isnot-nnm-p'.
166 ;; - t: FUNCTION takes the `call-next-method' function as its first (extra)
167 ;; argument.
168 (call-con nil :read-only t :type symbol) 173 (call-con nil :read-only t :type symbol)
169 (function nil :read-only t :type function)) 174 (function nil :read-only t :type function))
170 175
@@ -181,7 +186,10 @@ also passed as second argument to SPECIALIZERS-FUNCTION."
181 ;; The most important dispatch is last in the list (and the least is first). 186 ;; The most important dispatch is last in the list (and the least is first).
182 (dispatches nil :type (list-of (cons natnum (list-of generalizers)))) 187 (dispatches nil :type (list-of (cons natnum (list-of generalizers))))
183 (method-table nil :type (list-of cl--generic-method)) 188 (method-table nil :type (list-of cl--generic-method))
184 (options nil :type list)) 189 (options nil :type list)
190 ;; This slot holds the function we put into `symbol-function' before
191 ;; the actual dispatch function has been computed.
192 (lazy-function nil))
185 193
186(defun cl-generic-function-options (generic) 194(defun cl-generic-function-options (generic)
187 "Return the options of the generic function GENERIC." 195 "Return the options of the generic function GENERIC."
@@ -658,8 +666,6 @@ The set of acceptable TYPEs (also called \"specializers\") is defined
658 ;; Keep the ordering; important for methods with :extra qualifiers. 666 ;; Keep the ordering; important for methods with :extra qualifiers.
659 (mapcar (lambda (x) (if (eq x (car me)) method x)) mt))) 667 (mapcar (lambda (x) (if (eq x (car me)) method x)) mt)))
660 (let ((sym (cl--generic-name generic)) ; Actual name (for aliases). 668 (let ((sym (cl--generic-name generic)) ; Actual name (for aliases).
661 ;; FIXME: Try to avoid re-constructing a new function if the old one
662 ;; is still valid (e.g. still empty method cache)?
663 (gfun (cl--generic-make-function generic))) 669 (gfun (cl--generic-make-function generic)))
664 (cl-pushnew `(cl-defmethod . ,(cl--generic-load-hist-format 670 (cl-pushnew `(cl-defmethod . ,(cl--generic-load-hist-format
665 (cl--generic-name generic) 671 (cl--generic-name generic)
@@ -827,9 +833,24 @@ You might need to add: %S"
827 ,@fixedargs args))))))))) 833 ,@fixedargs args)))))))))
828 834
829(defun cl--generic-make-function (generic) 835(defun cl--generic-make-function (generic)
830 (cl--generic-make-next-function generic 836 "Return the function to put into the `symbol-function' of GENERIC."
831 (cl--generic-dispatches generic) 837 ;; The function we want is the one that performs the dispatch,
832 (cl--generic-method-table generic))) 838 ;; but that function depends on the set of methods and needs to be
839 ;; flushed/recomputed when the set of methods changes.
840 ;; To avoid reconstructing such a method N times for N `cl-defmethod',
841 ;; we construct the dispatch function lazily:
842 ;; we first return a "lazy" function, which waits until the
843 ;; first call to the method to really compute the dispatch function,
844 ;; at which point we replace the dummy with the real one.
845 (with-memoization (cl--generic-lazy-function generic)
846 (lambda (&rest args)
847 (let ((real
848 (cl--generic-make-next-function generic
849 (cl--generic-dispatches generic)
850 (cl--generic-method-table generic))))
851 (let ((current-load-list nil))
852 (defalias (cl--generic-name generic) real))
853 (apply real args)))))
833 854
834(defun cl--generic-make-next-function (generic dispatches methods) 855(defun cl--generic-make-next-function (generic dispatches methods)
835 (let* ((dispatch 856 (let* ((dispatch
@@ -985,10 +1006,6 @@ FUN is the function that should be called when METHOD calls
985 (setq methods (nreverse (mapcar #'cdr (sort methods #'car-less-than-car)))) 1006 (setq methods (nreverse (mapcar #'cdr (sort methods #'car-less-than-car))))
986 (cl--generic-make-next-function generic dispatches-left methods))) 1007 (cl--generic-make-next-function generic dispatches-left methods)))
987 1008
988(unless (ignore-errors (cl-generic-generalizers t))
989 ;; Temporary definition to let the next defgenerics succeed.
990 (fset 'cl-generic-combine-methods #'cl--generic-standard-method-combination))
991
992(cl-defgeneric cl-generic-generalizers (specializer) 1009(cl-defgeneric cl-generic-generalizers (specializer)
993 "Return a list of generalizers for a given SPECIALIZER. 1010 "Return a list of generalizers for a given SPECIALIZER.
994To each kind of `specializer', corresponds a `generalizer' which describes 1011To each kind of `specializer', corresponds a `generalizer' which describes
@@ -1031,8 +1048,7 @@ those methods.")
1031(unless (ignore-errors (cl-generic-generalizers t)) 1048(unless (ignore-errors (cl-generic-generalizers t))
1032 ;; Temporary definition to let the next defmethod succeed. 1049 ;; Temporary definition to let the next defmethod succeed.
1033 (fset 'cl-generic-generalizers 1050 (fset 'cl-generic-generalizers
1034 (lambda (specializer) 1051 (lambda (_specializer) (list cl--generic-t-generalizer)))
1035 (if (eq t specializer) (list cl--generic-t-generalizer))))
1036 (fset 'cl-generic-combine-methods #'cl--generic-standard-method-combination)) 1052 (fset 'cl-generic-combine-methods #'cl--generic-standard-method-combination))
1037 1053
1038(cl-defmethod cl-generic-generalizers (specializer) 1054(cl-defmethod cl-generic-generalizers (specializer)