Subject: Re: bug#77725: 31.0.50; Add support for types accepted by `cl-typep' to cl-generic?

From: Stefan Monnier <[email-masked]>
Date: Mon, 14 Apr 2025 14:27:00 -0400

> I understand more how cl-generic works, and I reworked gtype.el
> (attached) according to your inputs and my better understanding.

Looks good, thanks.

Some comments below.

> (eval-when-compile (require 'cl-macs))  ;For cl--find-class.

You can also use `cl-find-class` which doesn't require this gymnastics.

> (define-inline gtype-p (object)
>   "Return non-nil if the type of OBJECT is a gtype."
>   (inline-letevals (object)
>     (inline-quote
>      (and (symbolp ,object)
>           (eq (type-of (cl--find-class ,object)) 'gtype-class)))))

I hope this function is not speed-critical (and so can be a plain `defun`).

> (define-inline gtype-parents (name)
>   "Get parents of type with NAME.
> NAME is a symbol representing a type.
> Return a possibly empty list of types."
>   (inline-quote
>    (cl--class-allparents (cl--find-class ,name))))

Same here.

> (defun gtype--topologic-sort (graph &optional tie-breaker)

How is this different from `merge-ordered-lists`?

> ;;;###autoload
> (defmacro defgtype (name &optional parents &rest args)
>   "Define NAME as a gtype that inherits from PARENTS.

Inheritance != subtyping.  Better stick to the term "subtype" here since
there doesn't seem to be any inheritance going on here.

> If a gtype with NAME already exists, replace it.
>
> NAME is an unquoted symbol representing a gtype.
>
> Optional argument PARENTS is either an unquoted symbol or list of
> symbols other than NAME representing types parents of NAME.
> PARENTS nil or omitted means that NAME is a root type.
>
> Also pass NAME and ARGS to `cl-deftype' to define NAME as a new data
> type."

I get the impression that the role/impact of PARENTS is not explained
clearly enough.  The explanation should be clear enough that the reader
can infer more or less what could happen if a parent is missing or if
a non-parent is incorrectly listed as a parent.

>   (declare (debug cl-defmacro) (doc-string 4) (indent 3))
>   (cl-with-gensyms (err)
>     `(condition-case ,err
>          (progn
>            (cl-deftype ,name ,@args)
>            (gtype--register ',name ',parents ',args))
>        (error
>         (gtype--unregister ',name)
>         (error (error-message-string ,err))))))

Could we merge this into `cl-deftype`?

> (defmacro remgtype (name)

Try and stay within the namespace prefix.

> (defun gtype-of (object)
>   "Return the most specific possible gtype OBJECT belongs to.
> Return an unique list of types OBJECT belongs to, ordered from the
> most specific type to the most general."
>   ;; Ignore recursive call on the same OBJECT, which can legitimately
>   ;; occur when, for example, a parent type is investigating whether
>   ;; an object's type is that of one of its children.
>   (unless (eq object gtype--object)
>     (let ((gtype--object object)
>           (found (list (cl-type-of object))))
>       ;; Collect all gtypes OBJECT belongs to.
>       (dolist (gtype gtype--list)
>         (if (cl-typep object gtype)
>             (push gtype found)))
>       ;; Return an unique value of type.
>       (with-memoization (gethash found gtype--unique)
>         found))))

Since `gtype-of` is the function used to get the "tagcode" used for
method-dispatch, it is speed-critical.
But the above looks rather costly.  🙁

I think we could reduce this problem significantly with reasonably
simple changes to `cl-generic`: we could provide to the TAGCODE-FUNCTION
the list of specializers used by methods of the current
generic-function, so we'd only need to check those types which are
actually useful for the current dispatch.
[ Hmm... well, maybe not completely "simple" since it might have
  some problematic impact on the `cl--generic-prefill-dispatchers`
  mechanism, but that should be solvable.  ]

> (defun gtype--specializers (tag &rest _)
>   "If TAG is a gtype, return its specializers."
>   (and (consp tag) (gtype-p (car tag))
>        (with-memoization (gethash tag gtype--specializers)
>          (merge-ordered-lists (mapcar #'gtype-parents tag)))))

AFAIK this function is only ever called with "tagcodes" returned by your
own TAGCODE-FUNCTION, so I think the `gtype-p` test is redundant.

        Stefan