--- Begin Message ---
Subject: |
[PATCH] lisp/emacs-lisp/eieio.el (initialize-instance): Fix initform |
Date: |
Wed, 30 Jun 2021 12:45:27 +0000 |
signature.asc
Description: PGP signature
0001-lisp-emacs-lisp-eieio.el-initialize-instance-Fix-ini.patch
Description: Fix eval initform
* Summary
According to CLHS,
#+begin_quote
A default initial value form for a slot is defined by using the
:initform slot option to defclass. If no initialization argument
associated with that slot is given as an argument to make-instance or is
defaulted by :default-initargs, this default initial value form is
evaluated in the lexical environment of the defclass form that defined
it, and the resulting value is stored in the slot.
#+end_quote
--- [[clhs::07_a.htm][Object Creation and Initialization]]
This is not what happens in eieio. Rather, initform is evaluated even
when initarg is present.
* Emacs Lisp examples
Define a class with a slot that has an initarg and an initform involving
undefined function:
#+begin_src emacs-lisp
(defclass test-initform ()
((test-slot :initarg :test-slot :initform (identity (non-existent)))))
#+end_src
Now,
#+begin_src emacs-lisp :results code :wrap example emacs-lisp
(condition-case err (make-instance 'test-initform :test-slot 0)
(t err))
#+end_src
#+RESULTS:
#+begin_example emacs-lisp
(void-function non-existent)
#+end_example
Even though initform should not be evaluated since initarg is provided.
* Other references from CLHS
#+begin_quote
If the initialization argument has a value in the initialization
argument list, the value is stored into the slot of the newly created
object, overriding any :initform form associated with the slot.
#+end_quote
--- [[clhs::07_aa.htm][Initialization Arguments]]
#+begin_quote
An :initform form is used to initialize a slot only if no initialization
argument associated with that slot is given as an argument to
make-instance or is defaulted by :default-initargs.
#+end_quote
--- [[clhs::07_ac.htm][Defaulting of Initialization Arguments]]
#+begin_quote
If a slot has both an :initform form and an :initarg slot option, and
the initialization argument is defaulted using :default-initargs or is
supplied to make-instance, the captured :initform form is neither used
nor evaluated.
#+end_quote
--- [[clhs::07_ad.htm][Rules for Initialization Arguments]]
* Common Lisp examples
Define a class in exactly the same way as in Emacs Lisp:
#+begin_src lisp :results errors :wrap example lisp
(defclass test-initform ()
((test-slot :initarg :test-slot :initform (identity (non-existent)))))
#+end_src
#+RESULTS:
#+begin_example lisp
; in: DEFCLASS TEST-INITFORM
; (NON-EXISTENT)
;
; caught STYLE-WARNING:
; undefined function: COMMON-LISP-USER::NON-EXISTENT
;
; compilation unit finished
; Undefined function:
; NON-EXISTENT
; caught 1 STYLE-WARNING condition
#+end_example
Appropriately,
#+begin_src lisp :results value verbatim :wrap example lisp
(ignore-errors (make-instance 'test-initform))
#+end_src
#+RESULTS:
#+begin_example lisp
NIL
#<UNDEFINED-FUNCTION NON-EXISTENT {10052CF123}>
#+end_example
But with initarg, there is no need to evaluate initform, and it is not
evaluated:
#+begin_src lisp :results value verbatim :wrap example lisp
(make-instance 'test-initform :test-slot 0)
#+end_src
#+RESULTS:
#+begin_example lisp
#<TEST-INITFORM {1005224E13}>
#+end_example
* Notes
** Initializing to quoted initform
Emacs 27 source claims:
#+begin_quote
Paul Landes said in an email:
> CL evaluates it if it can, and otherwise, leaves it as
> the quoted thing as you already have. This is by the
> Sonya E. Keene book and other things I've look at on the
> web.
#+end_quote
--- [[file:/usr/share/emacs/27.2/lisp/emacs-lisp/eieio.el::;; Paul Landes said
in an email:][EIEIO on initform quoting]]
And eieio does quote initform forms with cars being symbols that are not fbound:
#+begin_src lisp :results none
(defclass test-initform-quote ()
((test-slot :initform (non-existent))))
#+end_src
#+begin_src emacs-lisp :results code :wrap example emacs-lisp
(slot-value (make-instance 'test-initform-quote) 'test-slot)
#+end_src
#+RESULTS:
#+begin_example emacs-lisp
(non-existent)
#+end_example
There is however no reference to Sonya E. Keene or anything else. Meanwhile,
#+begin_src lisp :results errors :wrap example lisp
(defclass test-initform-quote ()
((test-slot :initform (non-existent))))
#+end_src
#+RESULTS:
#+begin_example lisp
; in: DEFCLASS TEST-INITFORM-QUOTE
; (NON-EXISTENT)
;
; caught STYLE-WARNING:
; undefined function: COMMON-LISP-USER::NON-EXISTENT
;
; compilation unit finished
; Undefined function:
; NON-EXISTENT
; caught 1 STYLE-WARNING condition
#+end_example
#+begin_src lisp :results value verbatim :wrap example emacs-lisp
(ignore-errors (make-instance 'test-initform-quote))
#+end_src
#+RESULTS:
#+begin_example emacs-lisp
NIL
#<UNDEFINED-FUNCTION NON-EXISTENT {1003251A33}>
#+end_example
Also, when discussing interplay between initform and default-initargs, Sonya E.
Keene mentions:
#+begin_quote
The value of an :initform is evaluated each time it is used to initialize a
slot.
#+end_quote
--- Sonya E. Keene, Object Oriented Programming in Common Lisp:
A Programmer's Guide. 9.3. Controlling Initialization
With ~defclass~ Options
similarly stated in CLHS, in “Macro DEFCLASS”.
Emacs 28 source removes the claim but adds
#+begin_quote
FIXME: Historically, EIEIO used a heuristic to try and guess
whether the initform is a form to be evaluated or just
a constant. We use `eieio--eval-default-p' to see what the
heuristic says and if it disagrees with normal evaluation
then tweak the initform to make it fit and emit
a warning accordingly.
#+end_quote
--- [[file:~/src/emacs/lisp/emacs-lisp/eieio.el::;; FIXME: Historically, EIEIO
used a heuristic to try and guess][eieio in git]]
which arguably makes matters even more confusing. There should be no
such heuristic.
* Patch
We offer a patch for Emacs 28 (master) but we can't build Emacs 28 (the
bug had been filed) so it's untested and “works for me”.
--- End Message ---