Discussion:
c2mop:standard-class not used in lispworks
Piotr Wasik
2013-11-02 21:03:06 UTC
Permalink
Hi,

I have tried the following in LispWorks:

CL-USER 1 > (c2cl:defclass foo () ((s1)))
#<STANDARD-CLASS FOO 201010AB>

CL-USER 2 > (c2mop:slot-boundp-using-class (find-class 'foo)
(make-instance 'foo) (first (harlequin-common-lisp:class-slots
(find-class 'foo))))

Error: The slot #<STANDARD-EFFECTIVE-SLOT-DEFINITION S1 23A4C803> is
missing from #<FOO 23A4C2B7> (of class #<STANDARD-CLASS FOO 201010AB>)


The thing is that specialisation defined in
https://github.com/mcna/closer-mop/blob/master/closer-lispworks.lisp uses
standard-class from c2mop, which is later exported, but nothing forces
subsequent code to use it instead of cl:standard-class.

Of course (eq (find-class 'cl:standard-class) (find-class
'c2mop:standard-class)) --> NIL

When I explicitely use c2mop:standard-class however, the specialisation
from closer-mop.lisp is used:

(c2cl:defclass foo () ((s1)) (:metaclass c2mop:standard-class))
(c2mop:slot-boundp-using-class (find-class 'foo) (make-instance 'foo)
(first (harlequin-common-lisp:class-slots (find-class 'foo))))
NIL


As expected. How can it be fixed please? Is there any test suite that can
check what other functionality is affected?

Cheers,
Piotr
Pascal Costanza
2013-11-03 00:15:55 UTC
Permalink
Hi,

Unfortunately, it’s not possible to fix this.

In Closer to MOP, I have to balance a couple of requirements, and I cannot fulfill them all.

In LispWorks, the default protocol for slot accesses is based on symbols, not on effective slot definition objects. (This is how the CLOS MOP draft specifications have been defined up until very late in the CLOS MOP specification process.)

So by default, you say something like (slot-value-using-class (find-class ‘foo) (make-instance ‘foo) ‘s1), which is understood by LispWorks’s CLOS implementation (or better yet, just the plain (slot-value (make-instance ‘foo) ’s1)).

The goal of Closer to MOP is to change the protocols so they are more in line with what the actual CLOS MOP specification says. In order to be able to do this, I have to provide two specializations on slot-value-using-class and friends. The first is a method that still dispatches on slot names that then calls slot-value-using-class again with the respective effective slot definition object. The method looks like this:

(cl:defmethod slot-value-using-class
((class standard-class) object (slot symbol))
(declare (optimize (speed 3) (debug 0) (safety 0)
(compilation-speed 0)))
(let ((slotd (find-slot slot class)))
(if slotd
(slot-value-using-class class object slotd)
(slot-missing class object slot 'slot-value))))

Now, I also need to define a method for effective slot definition objects, which is not provided by default in LispWorks, to make this all work. It looks like this:

(cl:defmethod slot-value-using-class
((class standard-class) object (slotd standard-effective-slot-definition))
(declare (optimize (speed 3) (debug 0) (safety 0)
(compilation-speed 0)))
(slot-value-using-class
(load-time-value (class-prototype (find-class 'cl:standard-class)))
object
(slot-definition-name slotd)))

By calling slot-value-using-class again on the slot name, but this time on the original cl:standard-class instead of Closer to MOP’s standard-class, I now get back the original LispWorks slot access.

I cannot specialize the first method on cl:standard-class. This doesn’t work, because the CLOS MOP specification explicitly requires that each user-defined method on the standard protocol generic functions must have at least one argument specialized on one’s own class. I don’t have a specific class for the second argument at all, and the third argument must be specialized on symbol, so what I’m left with is to specialize the first argument on c2cl:standard-class. The same holds for the second method: There is no specific specializer available for the second argument, and the third argument must be standard-effective-slot-definition. (It is imaginable that the third argument is a subclass of standard-effective-slot-definition, but that wouldn’t solve your issue either.)

If you think about it, it’s clear why the requirement of the CLOS MOP exists: If I were allowed to define methods on standard protocol functions that are specialized only on standard metaclasses, this would replace the default behavior of the CLOS implementation, and thus prevent CLOS from working at all.

One way out would be to “force” every class to be an instance of c2cl:standard-class instead of cl:standard-class, as you suggest. However, I decided against that because this would lead to a potentially severe performance overhead: CLOS implementations make a major effort to implement the protocols as efficiently as possible if only standard metaclasses are involved. However, c2cl:standard-class is not a standard metaclass, so the CLOS implementation would have to resort to much slower generic implementations of the protocols. I decided it to be more important that you get best performance for parts of the protocol that are not specialized in any way for your own metaclasses, and only have an impact on subprotocols that you effectively change, as much as possible.

If you subclass c2cl:standard-class, you get the ‘fixed’ protocols, but if you don’t, you get the default implementation of LispWorks (same for any other CL implementation, as far as possible).

Yes, this means that you sometimes run into issues like the one you report. But I believe this is a lower price to pay than trying to get performance back that you may have lost with a more general implementation.

For your specific problem: Why do you need to call slot-boundp-using-class manually yourself? This should normally not be required, it’s normally better to just call slot-boundp…

Pascal
Post by Piotr Wasik
Hi,
CL-USER 1 > (c2cl:defclass foo () ((s1)))
#<STANDARD-CLASS FOO 201010AB>
CL-USER 2 > (c2mop:slot-boundp-using-class (find-class 'foo) (make-instance 'foo) (first (harlequin-common-lisp:class-slots (find-class 'foo))))
Error: The slot #<STANDARD-EFFECTIVE-SLOT-DEFINITION S1 23A4C803> is missing from #<FOO 23A4C2B7> (of class #<STANDARD-CLASS FOO 201010AB>)
The thing is that specialisation defined in https://github.com/mcna/closer-mop/blob/master/closer-lispworks.lisp uses standard-class from c2mop, which is later exported, but nothing forces subsequent code to use it instead of cl:standard-class.
Of course (eq (find-class 'cl:standard-class) (find-class 'c2mop:standard-class)) --> NIL
(c2cl:defclass foo () ((s1)) (:metaclass c2mop:standard-class))
(c2mop:slot-boundp-using-class (find-class 'foo) (make-instance 'foo) (first (harlequin-common-lisp:class-slots (find-class 'foo))))
NIL
As expected. How can it be fixed please? Is there any test suite that can check what other functionality is affected?
Cheers,
Piotr
--
Pascal Costanza
Piotr Wasik
2013-11-03 13:33:51 UTC
Permalink
Hi,

Thanks for your explanation. I do agree with your choice - built-in
performance optimisation over standardised MOP by default.

As of my specific problem, I did not write the code that calls MOP, it is
here:
https://github.com/erikg/ucw-core/blob/master/src/rerl/standard-component/standard-component.lispSearch
for slot-value-using-class and slot-boundp-using-class in this file.
It belongs to UCW library. It works fine in SBCL because SBCL uses 'native'
MOP, and I tried to run it in LispWorks. I do not deny it could be
rewritten to use more common slot-boundp and slot-value calls, but for now,
the simpler solution is to use c2mop:standard-class rather than
cl:standard-class. UCW uses its own standard-component-class so the price
in performance is already paid, as optimisations for standard-class you
mentioned are not used.

Cheers,
Piotr
Post by Pascal Costanza
Hi,
Unfortunately, it’s not possible to fix this.
In Closer to MOP, I have to balance a couple of requirements, and I cannot
fulfill them all.
In LispWorks, the default protocol for slot accesses is based on symbols,
not on effective slot definition objects. (This is how the CLOS MOP draft
specifications have been defined up until very late in the CLOS MOP
specification process.)
So by default, you say something like (slot-value-using-class (find-class
‘foo) (make-instance ‘foo) ‘s1), which is understood by LispWorks’s CLOS
implementation (or better yet, just the plain (slot-value (make-instance
‘foo) ’s1)).
The goal of Closer to MOP is to change the protocols so they are more in
line with what the actual CLOS MOP specification says. In order to be able
to do this, I have to provide two specializations on slot-value-using-class
and friends. The first is a method that still dispatches on slot names that
then calls slot-value-using-class again with the respective effective slot
(cl:defmethod slot-value-using-class
((class standard-class) object (slot symbol))
(declare (optimize (speed 3) (debug 0) (safety 0)
(compilation-speed 0)))
(let ((slotd (find-slot slot class)))
(if slotd
(slot-value-using-class class object slotd)
(slot-missing class object slot 'slot-value))))
Now, I also need to define a method for effective slot definition objects,
which is not provided by default in LispWorks, to make this all work. It
(cl:defmethod slot-value-using-class
((class standard-class) object (slotd
standard-effective-slot-definition))
(declare (optimize (speed 3) (debug 0) (safety 0)
(compilation-speed 0)))
(slot-value-using-class
(load-time-value (class-prototype (find-class 'cl:standard-class)))
object
(slot-definition-name slotd)))
By calling slot-value-using-class again on the slot name, but this time on
the original cl:standard-class instead of Closer to MOP’s standard-class, I
now get back the original LispWorks slot access.
I cannot specialize the first method on cl:standard-class. This doesn’t
work, because the CLOS MOP specification explicitly requires that each
user-defined method on the standard protocol generic functions must have at
least one argument specialized on one’s own class. I don’t have a specific
class for the second argument at all, and the third argument must be
specialized on symbol, so what I’m left with is to specialize the first
There is no specific specializer available for the second argument, and the
third argument must be standard-effective-slot-definition. (It is
imaginable that the third argument is a subclass of
standard-effective-slot-definition, but that wouldn’t solve your issue
either.)
If you think about it, it’s clear why the requirement of the CLOS MOP
exists: If I were allowed to define methods on standard protocol functions
that are specialized only on standard metaclasses, this would replace the
default behavior of the CLOS implementation, and thus prevent CLOS from
working at all.
One way out would be to “force” every class to be an instance of
c2cl:standard-class instead of cl:standard-class, as you suggest. However,
I decided against that because this would lead to a potentially severe
performance overhead: CLOS implementations make a major effort to implement
the protocols as efficiently as possible if only standard metaclasses are
involved. However, c2cl:standard-class is not a standard metaclass, so the
CLOS implementation would have to resort to much slower generic
implementations of the protocols. I decided it to be more important that
you get best performance for parts of the protocol that are not specialized
in any way for your own metaclasses, and only have an impact on
subprotocols that you effectively change, as much as possible.
If you subclass c2cl:standard-class, you get the ‘fixed’ protocols, but if
you don’t, you get the default implementation of LispWorks (same for any
other CL implementation, as far as possible).
Yes, this means that you sometimes run into issues like the one you
report. But I believe this is a lower price to pay than trying to get
performance back that you may have lost with a more general implementation.
For your specific problem: Why do you need to call slot-boundp-using-class
manually yourself? This should normally not be required, it’s normally
better to just call slot-boundp…
Pascal
Hi,
CL-USER 1 > (c2cl:defclass foo () ((s1)))
#<STANDARD-CLASS FOO 201010AB>
CL-USER 2 > (c2mop:slot-boundp-using-class (find-class 'foo) (make-instance 'foo) (first (harlequin-common-lisp:class-slots (find-class 'foo))))
Error: The slot #<STANDARD-EFFECTIVE-SLOT-DEFINITION S1 23A4C803> is missing from #<FOO 23A4C2B7> (of class #<STANDARD-CLASS FOO 201010AB>)
The thing is that specialisation defined in
https://github.com/mcna/closer-mop/blob/master/closer-lispworks.lisp uses
standard-class from c2mop, which is later exported, but nothing forces
subsequent code to use it instead of cl:standard-class.
Of course (eq (find-class 'cl:standard-class) (find-class
'c2mop:standard-class)) --> NIL
When I explicitely use c2mop:standard-class however, the specialisation
(c2cl:defclass foo () ((s1)) (:metaclass c2mop:standard-class))
(c2mop:slot-boundp-using-class (find-class 'foo) (make-instance 'foo) (first (harlequin-common-lisp:class-slots (find-class 'foo))))
NIL
As expected. How can it be fixed please? Is there any test suite that can
check what other functionality is affected?
Cheers,
Piotr
--
Pascal Costanza
Loading...