5

Q: can one symbol refer to a function, a variable, and a class?

Elisp is a Lisp-2 in which a symbol can have separate function and variable values. So, for example, I can define the following function and variable:

(defun kittens ()
  "Profess love of kittens."
  (message "I love kittens!"))

(defvar kittens nil
  "A variable to store information about kittens.")

And query their respective contents:

(symbol-value 'kittens)       ; => nil
(symbol-function 'kittens)    ; => (lambda nil "Profess love...")

However, once I define a class with the same name:

(defclass kittens ()
  ((quantity
    :initarg :quantity
    :initform 1000
    :accessor kittens-quantity
    :documentation "How many kittens anyone should strive to own."))
  "Kitten class.")

The class appears to supercede the function and the variable:

(symbol-value 'kittens)       ; => kittens
(symbol-function 'kittens)    ; => (lambda (&rest slots) "Create a new object...")

So: do classes simply clobber the original function and variable, or do the original values still exist somewhere?

5

The short answer is no.

Once you declare a class, Emacs will define a function with the name of that class as a creation function, thereby replacing your old function definition. In Emacs, eieio.el has a macro for defclass that goes like so:

(defmacro defclass (name superclasses slots &rest options-and-doc)

  ;; A lot of stuff here...

  ;; Non-abstract classes need a constructor.
  `(defun ,name (&rest slots)
     ,(format "Create a new object of class type `%S'." name)
     (declare (compiler-macro
               (lambda (whole)
                 (if (not (stringp (car slots)))
                     whole
                   (macroexp--warn-and-return
                    (format "Obsolete name arg %S to constructor %S"
                            (car slots) (car whole))
                    ;; Keep the name arg, for backward compatibility,
                    ;; but hide it so we don't trigger indefinitely.
                    `(,(car whole) (identity ,(car slots))
                      ,@(cdr slots)))))))
     (apply #'make-instance ',name slots))

Note that the macro runs (defun kittens (&rest slots) ....

It should be noted that the variable eieio-backward-compatibility (defaulted to t) controls whether your kittens tracks a variable after defclass.

For instance, when I define puppies without backward compatibility:

(setq-local eieio-backward-compatibility nil)

(defclass puppies ()
  ((quantity
    :initarg :quantity
    :initform 1000
    :accessor puppies-quantity
    :documentation "How many puppies anyone should strive to own."))
  "Puppies class!")

(symbol-function 'puppies)    ; => (lambda (&rest slots) "Create a new object...")
(symbol-value 'puppies)       ; => error: (void-variable puppies)

The function will still be overwritten, but if you have a variable set, it can remain.

  • 2
    That's true for the function, but the variable is only defined if eieio-backward-compatibility is non-nil. – Stefan Apr 26 at 0:37
  • @Stefan: that's super helpful information for my specific use case, but I couldn't find it in the manual. I presume that this variable is going to default to t into the distant future. – Dan Apr 26 at 8:56
  • 1
    For completeness, could @Stefan add his comment as a separate answer, or could cyberbisson incorporate Stefan's comment about the variable value into the original answer? – Dan Apr 26 at 8:58
  • Certainly. It's a good point. – cyberbisson Apr 26 at 11:22

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.