The following message is a courtesy copy of an article that has been posted to comp.lang.lisp as well.
Until now, I've been uncounsiously relying on something that the standard does not seem to specify: the fact that shared slots equipped with an initform are initialized before the first instance is created:
(defclass test () ((slot :allocation :class :initform t)))
(defmethod initialize-instance :before ((test test) &key) (format t "Slot boundp: ~A~%" (slot-boundp test 'slot)))
* (make-instance 'test) Slot boundp: T
* (make-instance 'test) Slot boundp: T
I just realized that because with ABCL (and contrary to the other implementations I've tried), the result is NIL, and then T.
But now, I'm a little confused. Section 7.1.3 of CLHS says:
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.
So after re-reading this (the "only" part notably), it now appears to me somewhat abusive to initialize shared slots earlier than at first instance creation time (like most lisps seem to do), because there's no way to know, at that time, whether the fist call to make-instance will provide an initarg.
WDYT?
Related to this is a question of style as well. I rarely use shared slots, but until now, I thought of it as a convenient way to encapsulate information that's supposed to be used by instances only (including the first one). Maybe this is not a good idea after all...
Tim Bradshaw pointed out that my message was not very clear because the test I gave does not demonstrate that an initform's result is used to initialize the slot early, only at which time a slot is initialized.
This on the other hand gets closer to the point:
(defclass test () ((slot :allocation :class :initform t :initarg :slot)))
(defmethod initialize-instance :before ((test test) &key) (format t "Slot value: ~A~%" (slot-value test 'slot)))
Fresh SBCL: CL-USER> (make-instance 'test) Slot value: T #<TEST {B8115E1}>
Fresh SBCL again: CL-USER> (make-instance 'test :slot nil) Slot value: T #<TEST {B92AC89}>
So it appears that even when an initarg is provided, the slot is still initialized to the iniform value prior to anything else.
On Wed, Jan 05, 2011 at 06:15:34PM +0100, Didier Verna wrote:
Tim Bradshaw pointed out that my message was not very clear because the test I gave does not demonstrate that an initform's result is used to initialize the slot early, only at which time a slot is initialized.
This on the other hand gets closer to the point:
(defclass test () ((slot :allocation :class :initform t :initarg :slot)))
(defmethod initialize-instance :before ((test test) &key) (format t "Slot value: ~A~%" (slot-value test 'slot)))
Fresh SBCL: CL-USER> (make-instance 'test) Slot value: T #<TEST {B8115E1}>
Fresh SBCL again: CL-USER> (make-instance 'test :slot nil) Slot value: T #<TEST {B92AC89}>
So it appears that even when an initarg is provided, the slot is still initialized to the iniform value prior to anything else.
Ecuse my ignorance, but what are you testing? During your second make-instance call the (initialize-instance :before ...) method is _not_ picking up the initform but the actual slot value from the class (remember: slot has class allocation and it's value gets initialized in the first invocation of make-instance 'test).
Cheers, Ralf Mattes
-- Resistance is futile. You will be jazzimilated.
Scientific site: http://www.lrde.epita.fr/~didier Music (Jazz) site: http://www.didierverna.com
pro mailing list pro@common-lisp.net http://common-lisp.net/cgi-bin/mailman/listinfo/pro
rm@tuxteam.de wrote:
On Wed, Jan 05, 2011 at 06:15:34PM +0100, Didier Verna wrote:
Tim Bradshaw pointed out that my message was not very clear because the test I gave does not demonstrate that an initform's result is used to initialize the slot early, only at which time a slot is initialized.
This on the other hand gets closer to the point:
(defclass test () ((slot :allocation :class :initform t :initarg :slot)))
(defmethod initialize-instance :before ((test test) &key) (format t "Slot value: ~A~%" (slot-value test 'slot)))
Fresh SBCL: CL-USER> (make-instance 'test) Slot value: T #<TEST {B8115E1}>
Fresh SBCL again: CL-USER> (make-instance 'test :slot nil) Slot value: T #<TEST {B92AC89}>
So it appears that even when an initarg is provided, the slot is still initialized to the iniform value prior to anything else.
Ecuse my ignorance, but what are you testing? During your second make-instance call the (initialize-instance :before ...) method is _not_ picking up the initform but the actual slot value from the class (remember: slot has class allocation and it's value gets initialized in the first invocation of make-instance 'test).
No, I said "Fresh SBCL /again/". SBCL is restarted brand new so that both of the calls to make-instance above are creating the /first/ instance of that class.
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
Hello!
In chapter 7.1.5, the fourth bullet of shared-initialize is:
"Any slots indicated by the second argument that are still unbound at this point are initialized according to their :initform forms."
There is no hint that "slots" could refer only to local slots, so I think that combined with the following from chapter 7.1.4:
"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."
there seems to be a strong hint that the behaviour you describe is actually not as intended by the specification: :initform forms should only be evaluated during shared-initialize.
As to your style question: I think that a class-allocated slot expresses primarily the invariant that this value is always the same for each instance. I see no problem with the encapsulation idea, though.
Best wishes Svante
Am 05.01.2011 23:08, schrieb Didier Verna:
rm@tuxteam.de wrote:
On Wed, Jan 05, 2011 at 06:15:34PM +0100, Didier Verna wrote:
Tim Bradshaw pointed out that my message was not very clear because the test I gave does not demonstrate that an initform's result is used to initialize the slot early, only at which time a slot is initialized.
This on the other hand gets closer to the point:
(defclass test () ((slot :allocation :class :initform t :initarg :slot)))
(defmethod initialize-instance :before ((test test) &key) (format t "Slot value: ~A~%" (slot-value test 'slot)))
Fresh SBCL: CL-USER> (make-instance 'test) Slot value: T #<TEST {B8115E1}>
Fresh SBCL again: CL-USER> (make-instance 'test :slot nil) Slot value: T #<TEST {B92AC89}>
So it appears that even when an initarg is provided, the slot is still initialized to the iniform value prior to anything else.
Ecuse my ignorance, but what are you testing? During your second make-instance call the (initialize-instance :before ...) method is _not_ picking up the initform but the actual slot value from the class (remember: slot has class allocation and it's value gets initialized in the first invocation of make-instance 'test).
No, I said "Fresh SBCL /again/". SBCL is restarted brand new so that both of the calls to make-instance above are creating the /first/ instance of that class.
- -- Svante Carl v. Erichsen Wentorfer Str. 96 21029 Hamburg
+49-(0)40-34923721 +49-(0)160-6941474 Svante.v.Erichsen@web.de
"Svante Carl v. Erichsen" Svante.v.Erichsen@web.de wrote:
from chapter 7.1.4:
"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."
This is also a good point, although Section 7.1 is entitled "Object Creation and Initialization", so it's probably not meant to describe what can or cannot happen before the first instance is created.
On Wed, Jan 5, 2011 at 7:17 AM, Didier Verna didier@lrde.epita.fr wrote:
Until now, I've been uncounsiously relying on something that the standard does not seem to specify: the fact that shared slots equipped with an initform are initialized before the first instance is created:
(defclass test () ((slot :allocation :class :initform t)))
(defmethod initialize-instance :before ((test test) &key) (format t "Slot boundp: ~A~%" (slot-boundp test 'slot)))
- (make-instance 'test)
Slot boundp: T
- (make-instance 'test)
Slot boundp: T
I just realized that because with ABCL (and contrary to the other implementations I've tried), the result is NIL, and then T.
But now, I'm a little confused. Section 7.1.3 of CLHS says:
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.
So after re-reading this (the "only" part notably), it now appears to me somewhat abusive to initialize shared slots earlier than at first instance creation time (like most lisps seem to do), because there's no way to know, at that time, whether the fist call to make-instance will provide an initarg.
WDYT?
It's possible that the CLHS is not perfectly consistent on this point, but there are a couple of places where it endorses the familiar interpretation. In section 7.1 it says: "The :initform for a shared slot may be used when defining or redefining the class." On the DEFCLASS page it says "... for shared slots, the dynamic environment [to be used when evaluating the :initform form] is the dynamic environment in which the defclass form was evaluated."
I think the sentence from 7.1.3 that you've quoted is intended to apply only to local slots; I think these other two passages make that clear by establishing that shared slots are indeed initialized at defclass time.
Oh, and this appears to be a bug in ABCL, since the second passage I quoted above _requires_ the :initform to be evaluated at defclass time.
-- Scott
"Scott L. Burson" Scott@sympoiesis.com wrote:
Oh, and this appears to be a bug in ABCL, since the second passage I quoted above _requires_ the :initform to be evaluated at defclass time.
That's a good point.
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
Hi!
Am 06.01.2011 01:31, schrieb Scott L. Burson:
In section 7.1 it says: "The :initform for a shared slot may be used when defining or redefining the class."
I think that this is the most directly applying passage for this issue. I am not entirely sure about the exact intended meaning of "may be" and "used", but at first glance it seems to me that this means that both the "SBCL way" and the "ABCL way" do conform. Portable code thus should not rely on either behaviour; it seems that whether :initform forms of shared slots are evaluated during definition or during first instanciation is unspecified.
Best wishes Svante
On Thu, 06 Jan 2011 10:33:43 +0100, Svante Carl v Erichsen said:
Am 06.01.2011 01:31, schrieb Scott L. Burson:
In section 7.1 it says: "The :initform for a shared slot may be used when defining or redefining the class."
I think that this is the most directly applying passage for this issue. I am not entirely sure about the exact intended meaning of "may be" and "used", but at first glance it seems to me that this means that both the "SBCL way" and the "ABCL way" do conform. Portable code thus should not rely on either behaviour; it seems that whether :initform forms of shared slots are evaluated during definition or during first instanciation is unspecified.
Your last sentence is definitely not true because the dictionary page for defclass says that the shared slot initform is evaluated in "the dynamic environment in which the defclass form was evaluated".
I think it should really start "The value of the :initform for a shared slot..." The point here is that the value is remembered and is "used" when needed.
It says "may be" because it is not used if the slot is already bound.
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
Hi!
Am 06.01.2011 13:56, schrieb Martin Simmons:
On Thu, 06 Jan 2011 10:33:43 +0100, Svante Carl v Erichsen said:
Am 06.01.2011 01:31, schrieb Scott L. Burson:
In section 7.1 it says: "The :initform for a shared slot may be used when defining or redefining the class."
I think that this is the most directly applying passage for this issue. I am not entirely sure about the exact intended meaning of "may be" and "used", but at first glance it seems to me that this means that both the "SBCL way" and the "ABCL way" do conform. Portable code thus should not rely on either behaviour; it seems that whether :initform forms of shared slots are evaluated during definition or during first instanciation is unspecified.
Your last sentence is definitely not true because the dictionary page for defclass says that the shared slot initform is evaluated in "the dynamic environment in which the defclass form was evaluated".
I think it should really start "The value of the :initform for a shared slot..." The point here is that the value is remembered and is "used" when needed.
The same page also says that the :initform form is evaluated every time it is used to initialize a slot. The question is when this initialization should take place for shared slots, not in which lexical and dynamic environment (that is clearly specified).
After rereading section 4.3.6 about how redefinition of classes works, I think that your interpretation of "may be" is correct. I guess that it would not make sense to specify that initforms of shared slots are evaluated during redefinition but not definition of a class. I wish section 4.3.2 was as explicit as 4.3.6.
To summarize: I now think that :initform forms of shared slots are specified to be evaluated during class definition.
Best wishes Svante
On Wed, 5 Jan 2011 16:31:31 -0800, Scott L Burson said:
I think the sentence from 7.1.3 that you've quoted is intended to apply only to local slots; I think these other two passages make that clear by establishing that shared slots are indeed initialized at defclass time.
I think 7.1.3 is intended to apply to shared slots as well, because 7.1.1 explicitly states "An initialization argument that initializes a shared slot stores its value into the shared slot, replacing any previous value." (though don't forget that this only happens when the slot is unbound, i.e. typically the first time).
On Thu, 6 Jan 2011 12:36:48 GMT, Martin Simmons said:
On Wed, 5 Jan 2011 16:31:31 -0800, Scott L Burson said:
I think the sentence from 7.1.3 that you've quoted is intended to apply only to local slots; I think these other two passages make that clear by establishing that shared slots are indeed initialized at defclass time.
I think 7.1.3 is intended to apply to shared slots as well, because 7.1.1 explicitly states "An initialization argument that initializes a shared slot stores its value into the shared slot, replacing any previous value." (though don't forget that this only happens when the slot is unbound, i.e. typically the first time).
Sorry, forget that -- "initialization argument" here is different from "initform".
However, 7.1.5 says (about the system-supplied primary method for shared-initialize) "This method behaves as follows on each slot, whether shared or local" so I still think 7.1.3 applies to shared slots as well.
On Wed, 05 Jan 2011 16:17:09 +0100, Didier Verna said:
Until now, I've been uncounsiously relying on something that the standard does not seem to specify: the fact that shared slots equipped with an initform are initialized before the first instance is created:
(defclass test () ((slot :allocation :class :initform t)))
(defmethod initialize-instance :before ((test test) &key) (format t "Slot boundp: ~A~%" (slot-boundp test 'slot)))
- (make-instance 'test)
Slot boundp: T
- (make-instance 'test)
Slot boundp: T
I just realized that because with ABCL (and contrary to the other implementations I've tried), the result is NIL, and then T.
But now, I'm a little confused. Section 7.1.3 of CLHS says:
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.
So after re-reading this (the "only" part notably), it now appears to me somewhat abusive to initialize shared slots earlier than at first instance creation time (like most lisps seem to do), because there's no way to know, at that time, whether the fist call to make-instance will provide an initarg.
WDYT?
The initarg will always set the slot anyway, so the only difference is that it has the "wrong" value transiently.
Related to this is a question of style as well. I rarely use shared slots, but until now, I thought of it as a convenient way to encapsulate information that's supposed to be used by instances only (including the first one). Maybe this is not a good idea after all...
Overall, I think shared slots are a bad idea. As discussed in a previous topic, defclass by itself doesn't provide very good encapsulation.
Another gotcha is that reevaluating a defclass with a different value for the initform doesn't reinitialize the slot.
This conversation has been good. I agree with both Svante and Scott.
As is often the case with the spec, it is not quite unambiguous. It seems to me that the spec is having trouble deciding whether it is a real, strict, firm language spec, or a useful tutorial/reference documentation aimed at what programmers care about the most. Both are worthy things to do but they can be at odds with each other in some cases. For class slots, I think some parts of the spec may have been written without class slots in mind, forcing all of us to be "language lawyers", infering things from fragments.
The Java Language Spec is like the first one, and when it comes to this topic it is utterly specific. (That's what happens when Guy Steele writes a spec; yes, I am a GLS fanboy...)
Shared slots are based on a venerable idea, from Smalltalk-80. But that language didn't have any other kind of variable besides slots of instances!
If you have class c1 with shared slots cs1 and cs2, why not just have two global variables (e.f. defvar) called *cs1* and *cs2* sitting next to the defclass?
At least one reason, and maybe the only reason, is that the initial value is figured out in a way that may depend on class precedence.
Whether that justifies the added complexity of having the feature is hard to prove one way or the other; it's a matter of opinion. I don't think I've ever used class slots.
-- Dan
Martin Simmons wrote:
Overall, I think shared slots are a bad idea. As discussed in a previous topic, defclass by itself doesn't provide very good encapsulation.
Another gotcha is that reevaluating a defclass with a different value for the initform doesn't reinitialize the slot.
Daniel Weinreb wrote:
This conversation has been good.
Yup. Here's what I gather from it, mostly:
- a shared slot's initform is required to be evaluated when the class is created, - however, nothing tells us that the resulting value is used immediately to initialize the slot (it could just be stored somewhere), and implementations seem to be free to do so right now, or later when the first instance is created.
On 6 Jan 2011, at 18:03, Didier Verna wrote:
Daniel Weinreb wrote:
This conversation has been good.
Yup. Here's what I gather from it, mostly:
- a shared slot's initform is required to be evaluated when the class is
created,
I agree here.
- however, nothing tells us that the resulting value is used immediately
to initialize the slot (it could just be stored somewhere), and implementations seem to be free to do so right now, or later when the first instance is created.
I disagree here. I think the only valid time to initialize a shared slot is when the first instance of the class in question is initialized, using either an explicitly passed value via an :initarg or :default-initargs option, or using the most recently provided :initform. What the CLOS MOP says about class-prototype is not binding in this case, according to the HyperSpec the only way to initialize instances of user-defined classes is by way of initialize-instance, reinitialize-instance and/or shared-initialize. This usually happens implicitly as an effect of calling make-instance.
This effectively means the following:
- When a class is defined for the first time, the :initform for a shared slot needs to be evaluated immediately, and its result must be stored in some temporary memory location. (There is no notion of first-class dynamic environments in Common Lisp, so there is no other way of ensuring that the :initform can be evaluated in the correct dynamic environment.)
- When a class is redefined before the first instance of that class is initialized, the :initform for a shared slot needs to be reevaluated and the result stored in that temporary memory location, possibly overriding a previously stored result of an initform.
- When a class is redefined after the first instance of that class is initialized, the :initform for a shared slot can be ignored, because that shared slot is already initialized.
- When the first instance of a class is initialized, the shared slots can be initialized with the values stored in the temporary memory locations for the results of the initforms, which can in turn be discarded.
So, effectively, a shared slot behaves like defparameter before the first instance of a class is initialized, and like defvar afterwards. This is pretty insane. It would have been easier and more straightforward if there had been a :reinitialize slot option for shared slots which, if true, makes it behave like defparameter, and if false, makes it behave like defvar. That would have been unambiguous semantics.
There is no real advantage in having shared slots over global special variables. On top of that, the slot access protocols in the CLOS MOP also don't work that well in conjunction with shared slots. So it's better to avoid them. Fortunately, this is the only feature in CLOS that doesn't make any sense, as far as I can tell.
Pascal
Daniel Weinreb wrote:
- however, nothing tells us that the resulting value is used immediately
to initialize the slot (it could just be stored somewhere), and implementations seem to be free to do so right now, or later when the first instance is created.
Pascal Costanza pc@p-cos.net wrote:
- When a class is defined for the first time, the :initform for a shared slot needs to be evaluated immediately, and its result must be stored in some temporary memory location. (There is no notion of first-class dynamic environments in Common Lisp, so there is no other way of ensuring that the :initform can be evaluated in the correct dynamic environment.)
No. You may immediately capture the lexical environment in a closure `(lambda () ,initform).
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
Hi!
Am 08.01.2011 11:22, schrieb Faré:
Daniel Weinreb wrote:
- however, nothing tells us that the resulting value is used immediately
to initialize the slot (it could just be stored somewhere), and implementations seem to be free to do so right now, or later when the first instance is created.
Pascal Costanza pc@p-cos.net wrote:
- When a class is defined for the first time, the :initform for a shared slot needs to be evaluated immediately, and its result must be stored in some temporary memory location. (There is no notion of first-class dynamic environments in Common Lisp, so there is no other way of ensuring that the :initform can be evaluated in the correct dynamic environment.)
No. You may immediately capture the lexical environment in a closure `(lambda () ,initform).
But that does not keep the /dynamic/ environment, as required by the DEFCLASS specification. Am I missing something obvious?
Best wishes Svante
On 8 Jan 2011, at 11:22, Faré wrote:
Daniel Weinreb wrote:
- however, nothing tells us that the resulting value is used immediately
to initialize the slot (it could just be stored somewhere), and implementations seem to be free to do so right now, or later when the first instance is created.
Pascal Costanza pc@p-cos.net wrote:
- When a class is defined for the first time, the :initform for a shared slot needs to be evaluated immediately, and its result must be stored in some temporary memory location. (There is no notion of first-class dynamic environments in Common Lisp, so there is no other way of ensuring that the :initform can be evaluated in the correct dynamic environment.)
No. You may immediately capture the lexical environment in a closure `(lambda () ,initform).
I meant the dynamic environment, not the lexical environment.
For example, ContextL provides a way to capture dynamic environments, but this requires using ContextL's binding constructs.
Pascal
On Fri, 7 Jan 2011 23:42:23 +0100, Pascal Costanza pc@p-cos.net wrote:
There is no real advantage in having shared slots over global special variables. On top of that, the slot access protocols in the CLOS MOP also don't work that well in conjunction with shared slots. So it's better to avoid them. Fortunately, this is the only feature in CLOS that doesn't make any sense, as far as I can tell.
This is sad, indeed, as shared slots could have been used to associate information with sub-lattice of the class relationship lattice. I have been tempted to do exactly this, multiple times, having, instead, to resort to manual storage of this information.
On 8 Jan 2011, at 14:15, Samium Gromoff wrote:
On Fri, 7 Jan 2011 23:42:23 +0100, Pascal Costanza pc@p-cos.net wrote:
There is no real advantage in having shared slots over global special variables. On top of that, the slot access protocols in the CLOS MOP also don't work that well in conjunction with shared slots. So it's better to avoid them. Fortunately, this is the only feature in CLOS that doesn't make any sense, as far as I can tell.
This is sad, indeed, as shared slots could have been used to associate information with sub-lattice of the class relationship lattice. I have been tempted to do exactly this, multiple times, having, instead, to resort to manual storage of this information.
What exactly would you like to do? There are ways to do such things using the CLOS MOP...
Pascal
On Sat, 8 Jan 2011 15:48:33 +0100, Pascal Costanza pc@p-cos.net wrote:
On 8 Jan 2011, at 14:15, Samium Gromoff wrote:
On Fri, 7 Jan 2011 23:42:23 +0100, Pascal Costanza pc@p-cos.net wrote:
There is no real advantage in having shared slots over global special variables. On top of that, the slot access protocols in the CLOS MOP also don't work that well in conjunction with shared slots. So it's better to avoid them. Fortunately, this is the only feature in CLOS that doesn't make any sense, as far as I can tell.
This is sad, indeed, as shared slots could have been used to associate information with sub-lattice of the class relationship lattice. I have been tempted to do exactly this, multiple times, having, instead, to resort to manual storage of this information.
What exactly would you like to do? There are ways to do such things using the CLOS MOP...
Ok, real world code, beware. While reading, only pay attention how VCS-TYPE-MIXIN threads through the "slices".
(defclass vcs-type-mixin () (# the following slot is absent in real code, I have to emulate it (enabled-p :accessor vcs-type-enabled-p :allocation :class) (vcs-type :reader vcs-type :initarg :vcs-type)))
;;;;; VCS slice (protocol classes) ;;; exhaustive partition of VCS-TYPE-MIXIN (defclass git (vcs-type-mixin) () (:default-initargs :vcs-type 'git)) (defclass nongit-mixin () ()) (defclass hg (vcs-type-mixin nongit-mixin) () (:default-initargs :vcs-type 'hg)) (defclass darcs (vcs-type-mixin nongit-mixin) () (:default-initargs :vcs-type 'darcs)) (defclass cvs (vcs-type-mixin wrinkle-mixin nongit-mixin) () (:default-initargs :vcs-type 'cvs)) (defclass svn (vcs-type-mixin wrinkle-mixin nongit-mixin) () (:default-initargs :vcs-type 'svn)) (defclass tarball (vcs-type-mixin nongit-mixin) () (:default-initargs :vcs-type 'tarball))
;;;;; product slice (protocol classes) ;;; exhaustive partition of type product of VCS-TYPE, TRANSPORT-MIXIN, CLONE-SEPARATION-MIXIN, and ;;; FETCH-INDIRECTION-MIXIN (defclass git-native (git native direct-fetch clone-is-fetch) () (:default-initargs :schema 'git)) (defclass git-http (git http direct-fetch clone-is-fetch) ()) ;;; ...... 8< ...... (defclass git-combined (git-native git-http combined) ()) ;;; ...... >8 ...... (defclass hg-http (hg http indirect-fetch separate-clone) ()) (defclass darcs-http (darcs http indirect-fetch separate-clone) ()) (defclass cvs-rsync (cvs rsync indirect-fetch clone-is-fetch) ()) (defclass cvs-native (cvs native direct-fetch clone-is-fetch) () (:default-initargs :schema '|:PSERVER|)) (defclass tarball-http (tarball http direct-fetch clone-is-fetch) ((initial-version :accessor initial-tarball-version :initarg :initial-version))) (defclass svn-rsync (svn rsync indirect-fetch clone-is-fetch) ()) ;;; ...... 8< ...... (defclass svn-direct (svn direct-fetch) ()) ;;; ...... >8 ...... (defclass svn-http (svn-direct http #| direct |# clone-is-fetch) ()) (defclass svn-native (svn-direct native #| direct |# clone-is-fetch) () (:default-initargs :schema 'svn))
;;;;; intermediate slice (protocol classes) ;;; Location * VCS (defclass git-remote (remote git) ()) (defclass darcs-remote (remote darcs) ()) (defclass hg-remote (remote hg) ()) (defclass cvs-remote (remote cvs) ()) (defclass svn-remote (remote svn) ()) (defclass tarball-remote (remote tarball) ())
;;;;; instantiable classes ;;; almost most specific (due to GATE mixin), exhaustive partition of REMOTE (defclass git-native-remote (git-remote git-native) ()) (defclass git-http-remote (git-remote git-http) ()) (defclass git-combined-remote (git-remote git-combined) ()) (defclass hg-http-remote (hg-remote hg-http) ()) (defclass darcs-http-remote (darcs-remote darcs-http) ()) (defclass cvs-rsync-remote (cvs-remote cvs-rsync) ()) (defclass cvs-native-remote (cvs-remote cvs-native) ()) (defclass svn-rsync-remote (svn-remote svn-rsync) ()) (defclass svn-http-remote (svn-remote svn-http) ()) (defclass svn-native-remote (svn-remote svn-native) ()) (defclass tarball-http-remote (tarball-remote tarball-http) ())
(defun vcs-enabled-p (type) "Problem function." (class-slot type 'enabled-p))
...I want to be able to use VCS-ENABLED-P on any slice before instantiable classes are instantiated.
On Sat, 08 Jan 2011 18:07:48 +0300, Samium Gromoff said:
On Sat, 8 Jan 2011 15:48:33 +0100, Pascal Costanza pc@p-cos.net wrote:
On 8 Jan 2011, at 14:15, Samium Gromoff wrote:
On Fri, 7 Jan 2011 23:42:23 +0100, Pascal Costanza pc@p-cos.net wrote:
There is no real advantage in having shared slots over global special variables. On top of that, the slot access protocols in the CLOS MOP also don't work that well in conjunction with shared slots. So it's better to avoid them. Fortunately, this is the only feature in CLOS that doesn't make any sense, as far as I can tell.
This is sad, indeed, as shared slots could have been used to associate information with sub-lattice of the class relationship lattice. I have been tempted to do exactly this, multiple times, having, instead, to resort to manual storage of this information.
What exactly would you like to do? There are ways to do such things using the CLOS MOP...
Ok, real world code, beware. While reading, only pay attention how VCS-TYPE-MIXIN threads through the "slices".
(defclass vcs-type-mixin () (# the following slot is absent in real code, I have to emulate it (enabled-p :accessor vcs-type-enabled-p :allocation :class) (vcs-type :reader vcs-type :initarg :vcs-type)))
;;;;; VCS slice (protocol classes) ;;; exhaustive partition of VCS-TYPE-MIXIN (defclass git (vcs-type-mixin) () (:default-initargs :vcs-type 'git)) (defclass nongit-mixin () ()) (defclass hg (vcs-type-mixin nongit-mixin) () (:default-initargs :vcs-type 'hg)) (defclass darcs (vcs-type-mixin nongit-mixin) () (:default-initargs :vcs-type 'darcs)) (defclass cvs (vcs-type-mixin wrinkle-mixin nongit-mixin) () (:default-initargs :vcs-type 'cvs)) (defclass svn (vcs-type-mixin wrinkle-mixin nongit-mixin) () (:default-initargs :vcs-type 'svn)) (defclass tarball (vcs-type-mixin nongit-mixin) () (:default-initargs :vcs-type 'tarball)) ... (defun vcs-enabled-p (type) "Problem function." (class-slot type 'enabled-p))
...I want to be able to use VCS-ENABLED-P on any slice before instantiable classes are instantiated.
Another reason why this won't work is that you only have one slot -- the same value is shared between every subclass of vcs-type-mixin. Adding a separate slot to each subclass is a pain.
On Mon, 10 Jan 2011 18:33:17 GMT, Martin Simmons martin@lispworks.com wrote:
Another reason why this won't work is that you only have one slot -- the same value is shared between every subclass of vcs-type-mixin. Adding a separate slot to each subclass is a pain.
Oh, right, my fault here. I remember elaborate macros, from my other attempts to use shared slots for this purpose, which solved exactly this problem of excess manual specification of "please make the sublattice rooted at this class a separate subdomain for this shared slot".
On 10 Jan 2011, at 19:58, Samium Gromoff wrote:
On Mon, 10 Jan 2011 18:33:17 GMT, Martin Simmons martin@lispworks.com wrote:
Another reason why this won't work is that you only have one slot -- the same value is shared between every subclass of vcs-type-mixin. Adding a separate slot to each subclass is a pain.
Oh, right, my fault here. I remember elaborate macros, from my other attempts to use shared slots for this purpose, which solved exactly this problem of excess manual specification of "please make the sublattice rooted at this class a separate subdomain for this shared slot".
You don't need shared slots, but class properties. ;) Here is a sketch (this assumes Closer to MOP is present, but should actually work without it in most CL implementations - warning, this is only lightly tested):
(in-package :closer-common-lisp-user)
(defclass property-class (standard-class) ((properties :initform '() :initarg :properties)))
(defmethod validate-superclass ((class property-class) (superclass standard-class)) t)
(defgeneric class-getf (class indicator &optional default) (:method ((class symbol) indicator &optional default) (class-getf (find-class class) indicator default)) (:method ((class property-class) indicator &optional default) (ensure-finalized class) (loop for class in (class-precedence-list class) do (multiple-value-bind (indicator value tail) (get-properties (slot-value class 'properties) (list indicator)) (when (or indicator value tail) (return value))) finally (return default))))
(defgeneric (setf class-getf) (new-value class indicater &optional default) (:method (new-value (class symbol) indicator &optional default) (declare (ignore default)) (setf (class-getf (find-class class) indicator) new-value)) (:method (new-value (class property-class) indicator &optional default) (declare (ignore default)) (setf (getf (slot-value class 'properties) indicator) new-value)))
(defclass test () () (:metaclass property-class) (:properties :a 1 :b 2))
(defclass test2 (test) () (:metaclass property-class) (:properties :a 3 :c 4))
C2CL-USER 21 > (class-getf 'test :a) 1
C2CL-USER 22 > (class-getf 'test :b) 2
C2CL-USER 23 > (class-getf 'test2 :a) 3
C2CL-USER 24 > (class-getf 'test2 :b) 2
C2CL-USER 27 > (class-getf 'test2 :c) 4
C2CL-USER 28 > (setf (class-getf 'test2 :b) 42) 42
C2CL-USER 29 > (class-getf 'test2 :b) 42
C2CL-USER 30 > (class-getf 'test :b) 2
This can probably be improved. I especially don't like the call to ensure-finalized - you could probably avoid that by first looking at the class itself before requesting the class precedence list, or so. I could have used alists, or 'real' slots, but plists are very convenient here because they mesh well with class metaobject initialization, and they don't require the heavy lifting of defining new metaclasses for new slots. One can imagine adding new methods on slot-value-using-class and friends that access class-getf behind the scenes, so you could actually access the class properties from plain class instances. Etc., etc. - you get the idea...
Best, Pascal
That sounds like a good way for it to work.
I'm not sure how safe it would be to depend on each of the extant implementation of Common Lisp to do this. It would probably be best if they all conformed; the only drawback would be the tiny possibility that some code somewhere is depending on the existing behavior. One could always use the old trick of having a compatibility mode for the sake of applications that want to move to the new release but whose developers don't have time to fix the application "the right way".
-- Dan
Pascal Costanza wrote:
On 6 Jan 2011, at 18:03, Didier Verna wrote:
Daniel Weinreb wrote:
This conversation has been good.
Yup. Here's what I gather from it, mostly:
- a shared slot's initform is required to be evaluated when the class is
created,
I agree here.
- however, nothing tells us that the resulting value is used immediately
to initialize the slot (it could just be stored somewhere), and implementations seem to be free to do so right now, or later when the first instance is created.
I disagree here. I think the only valid time to initialize a shared slot is when the first instance of the class in question is initialized, using either an explicitly passed value via an :initarg or :default-initargs option, or using the most recently provided :initform. What the CLOS MOP says about class-prototype is not binding in this case, according to the HyperSpec the only way to initialize instances of user-defined classes is by way of initialize-instance, reinitialize-instance and/or shared-initialize. This usually happens implicitly as an effect of calling make-instance.
This effectively means the following:
When a class is defined for the first time, the :initform for a shared slot needs to be evaluated immediately, and its result must be stored in some temporary memory location. (There is no notion of first-class dynamic environments in Common Lisp, so there is no other way of ensuring that the :initform can be evaluated in the correct dynamic environment.)
When a class is redefined before the first instance of that class is initialized, the :initform for a shared slot needs to be reevaluated and the result stored in that temporary memory location, possibly overriding a previously stored result of an initform.
When a class is redefined after the first instance of that class is initialized, the :initform for a shared slot can be ignored, because that shared slot is already initialized.
When the first instance of a class is initialized, the shared slots can be initialized with the values stored in the temporary memory locations for the results of the initforms, which can in turn be discarded.
So, effectively, a shared slot behaves like defparameter before the first instance of a class is initialized, and like defvar afterwards. This is pretty insane. It would have been easier and more straightforward if there had been a :reinitialize slot option for shared slots which, if true, makes it behave like defparameter, and if false, makes it behave like defvar. That would have been unambiguous semantics.
There is no real advantage in having shared slots over global special variables. On top of that, the slot access protocols in the CLOS MOP also don't work that well in conjunction with shared slots. So it's better to avoid them. Fortunately, this is the only feature in CLOS that doesn't make any sense, as far as I can tell.
Pascal
Daniel Weinreb wrote:
This conversation has been good. I agree with both Svante and Scott.
As is often the case with the spec, it is not quite unambiguous.
Oh, and I meant to say that I'm not sure the spec is 100% clear on which forms are evaluated v. not evaluated, and whether there are any guarantees on the order of evaluation.
Also, Didier saw that slots were being initiatlized before the initialize-instance method is called, whereas the spec seems to be saying that the default initialize-instance method takes responsibility for doing that, in 7.1:
"The slot-filling behavior described above is implemented by a system-supplied primary /method/ cid:part1.05080707.08000505@itasoftware.com for *initialize-instance* cid:part2.00040607.08020702@itasoftware.com which invokes *shared-initialize* cid:part3.06000701.07020506@itasoftware.com."
-- Dan
On Thu, 06 Jan 2011 11:44:13 -0500, Daniel Weinreb said:
If you have class c1 with shared slots cs1 and cs2, why not just have two global variables (e.f. defvar) called *cs1* and *cs2* sitting next to the defclass?
At least one reason, and maybe the only reason, is that the initial value is figured out in a way that may depend on class precedence.
Indeed (and also whether subclasses share the same value or have their own).