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.

  • (apply (intern (concat "make-" tag)) args) ? – politza Oct 6 '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 6 '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 6 '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 6 '17 at 22:30
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.