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?

Dan
  • 32,584
  • 6
  • 98
  • 168

1 Answers1

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.

Dan
  • 32,584
  • 6
  • 98
  • 168
cyberbisson
  • 887
  • 1
  • 6
  • 17
  • 2
    That's true for the function, but the variable is only defined if `eieio-backward-compatibility` is non-nil. – Stefan Apr 26 '19 at 00:37
  • @Stefan: that's super helpful information for my specific use case, but I couldn't find it in the [manual](https://www.gnu.org/software/emacs/manual/html_node/eieio/). I presume that this variable is going to default to `t` into the distant future. – Dan Apr 26 '19 at 08: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 '19 at 08:58
  • Certainly. It's a good point. – cyberbisson Apr 26 '19 at 11:22