3

What equivalents exist for EIEIO's make-instance in cl-lib?

(make-instance 'my/class :slot-a "value" :slot-b "value")

I would like to create an instance of an arbitrary struct identified by a supplied symbol without reliance on knowledge of the constructor or requiring some constructor convention (ex: assuming that make-[struct-name] exists).

It isn't essential that the method allow for initialization of the object with supplied values. It could be equivalent to the make-instance in accepting values for all slots with defined keywords or it could be equivalent to the default struct constructor (make-[struct-name]) and only create, tag, and initialize default values in the prescribed list/vector format.

ebpa
  • 7,319
  • 26
  • 53
  • `(apply (intern (concat "make-" tag)) args)` ? – politza Oct 06 '17 at 22:00
  • @politza I'm trying to avoid making the assumption that `make-[struct-name]` exists. The struct declaration may have included `(:constructor nil)` to prevent definition of the `make-[struct-name]` default constructor. – ebpa Oct 06 '17 at 22:07
  • Ok, that seems difficult without referring to implementation details. `defstruct` just expands to a bunch of variables and functions. – politza Oct 06 '17 at 22:13
  • @politza That would explain why I didn't find what I was hoping to find! My hope/assumption was that cl would preserve the struct definition in some way to enable a generalized instantiation function like `make-instance`. If that's not the case, a constructor-less struct is a rather effective way of declaring a struct abstract :-P – ebpa Oct 06 '17 at 22:30

1 Answers1

3

Actually, the cl-defstruct macro does preserve enough info to make such a function possible, but I don't think anyone has written such a beast yet.

The way this info is stored has changed in Emacs-25: now the macro expands to various other things plus a call to cl-struct-define which creates a class object (itself a struct). You can inspect it with C-h o cl-structure-class RET (which is the corresponding metaclass) and C-h o cl-slot-descriptor RET. And you can get the class object from the type name with (cl--find-class <type>).

You should even be able to extend make-instance with a method to handle defstruct types. E.g.:

(cl-defmethod make-instance ((class symbol) &rest slots)
  (let ((co (cl--find-class class)))
    (if co (apply #'make-instance co slots)
      (cl-call-next-method))))

(cl-defmethod make-instance ((sc cl-structure-class) &rest slots)
  (let* ((sds (cl--struct-class-slots sc))
         ;; For techno-historical reasons, the first slot should be the
         ;; type/symbol rather than the class.
         (o (make-record (cl--struct-class-name sc) (length sds) nil))
         (alist '()))
    (while slots
      (push (cons (intern (substring (symbol-name (pop slots)) 1))
                  (pop slots))
            alist))
    (dotimes (i (length sds))
      (pcase-let* (((cl-struct cl-slot-descriptor name initform)
                    (aref sds i)))
        (aset o (1+ i) (or (alist-get name alist) initform))))
    o))

Note: currently in Elisp, apply can be a performance problem (e.g. it's the main bottleneck in cl-print-object), so the above could be too slow for some use-cses.

Stefan
  • 26,154
  • 3
  • 46
  • 84