MOP protocol/constructors for conditions

Bug #1761735 reported by Michał "phoe" Herda
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
SBCL
Triaged
Wishlist
Unassigned

Bug Description

The following works (as in, does not error) on SBCL at the moment:

(define-condition my-condition () ())
(defmethod initialize-instance :after ((warning my-condition) &key)
  (print "boo"))

This constructor method is only called if I call:
    (make-instance 'my-condition)
and not
    (make-condition 'my-condition)

This behaviour is already present in CCL, ECL, CLISP, ABCL.

Please adapt MAKE-CONDITION to call MAKE-INSTANCE, so the constructors for INITIALIZE-INSTANCE are called.

It seems that these two ways of creating condition instances are independent:

CL-USER> (trace make-condition)
(MAKE-CONDITION)
CL-USER> (make-instance 'style-warning)
#<STYLE-WARNING {1002F8D193}>
CL-USER> (make-condition 'style-warning)
  0: (MAKE-CONDITION STYLE-WARNING)
  0: MAKE-CONDITION returned #<STYLE-WARNING {100305A933}>
#<STYLE-WARNING {100305A933}>

Revision history for this message
Michał "phoe" Herda (phoe-krk) wrote :

MAKE-CONDITION is not called in MAKE-INSTANCE as ALLOCATE-INSTANCE calls ALLOCATE-CONDITION directly.

https://github.com/sbcl/sbcl/blob/master/src/pcl/slots.lisp#L486

In a fully bootstrapped CL, it seems that the following redefinition does not break anything:

(defun make-condition (condition-type &rest args)
  (apply #'make-instance condition-type args))

The question is - what if MAKE-CONDITION is called before MAKE-INSTANCE is functional - or rather, should this old MAKE-CONDITION stay around until, late in the bootstrapping process, it is replaceable by the new version. It would also require the old MAKE-CONDITION be declared notinline so the redefinition is possible.

Revision history for this message
Stas Boukarev (stassats) wrote :

What standard section is this about?

Revision history for this message
Michał "phoe" Herda (phoe-krk) wrote :

This behavior is not defined by the standard. This is an extension request. Its purpose is to make SBCL's MAKE-CONDITION behave the same way as it does in other CL implementations which already have this extension.

Revision history for this message
Stas Boukarev (stassats) wrote :

That doesn't appear to be an extension but a side effect of conditions being implemented directly on top of clos objects.

Changed in sbcl:
status: New → Invalid
Revision history for this message
Michał "phoe" Herda (phoe-krk) wrote :

Nonetheless it provides me with the ability to create constructors for condition objects which are created in a standard way, through MAKE-CONDITION. SBCL currently has no such functionality.

Revision history for this message
Jan Moringen (scymtym) wrote :

I don't think this is any more invalid than other wishlist issues.

Changed in sbcl:
status: Invalid → Triaged
importance: Undecided → Wishlist
Revision history for this message
Stas Boukarev (stassats) wrote :

I'd prefer not to have a thing that should never be implemented to be permanently listed in the wishlist.
This is not standard and will help no one if it's implemented. The code using that will not only be non standard, but also not work on older SBCL versions
The standard is the only thing we can rely on to properly implement and optimize code. The reasoning "other implementations do it that way" is a slipper slope, where do we stop? I'd even prefer an error or a warning to be signalled, which is in line with how SBCL treats other non standard code. A language that is implicitly encoded by its implementation is a sad thing.

Revision history for this message
Michał "phoe" Herda (phoe-krk) wrote :

I am working on a library for defining and managing protocols, which are parts of interfaces. These protocols contain protocol classes, which are not meant to be instantiated directly - only their subclasses may be instantiated.

Currently I can prevent direct instances of protocol *classes* from being instantiated by creating a proper INITIALIZE-INSTANCE :BEFORE method. I cannot do the equivalent thing on SBCL because it has no way *at all* to write constructor methods for conditions.

What I actually need is a way to create constructors for condition objects. I don't care how they are implemented, but I would like to have them, since not having them prevents me from enforcing that aforementioned constraint. It would be nice to have them in a way consistent with other implementations, but I can do #+sbcl in my code if that's the way it should be.

The statement "will help no one if it's implemented" is therefore incorrect.

Revision history for this message
Stas Boukarev (stassats) wrote :

The standard clearly says "make-condition, not make-instance, must be used to create condition objects explicitly." I usually refrain from changing the language when trying to write my programs.

Revision history for this message
Michał "phoe" Herda (phoe-krk) wrote :

Okay. I have not worded myself clearly enough.

My goal is not to make MAKE-CONDITION call MAKE-INSTANCE.

My goal is to be able to write constructor functions or methods for condition types in SBCL that are evaluated every time MAKE-CONDITION is called.

Revision history for this message
Stas Boukarev (stassats) wrote :

That doesn't make it any more standard. And you want to change the language just to signal an error when instantiating a mixin?

Revision history for this message
Michał "phoe" Herda (phoe-krk) wrote :

I want to have a language extension that allows me to do exactly this - signal an error when I instantiate a mixin. Otherwise I am unable to enforce this constraint in my library.

The only workaround I can think of is defining a slot in the mixin condition with :initform (error ...) and have all subclasses override this slot with their own :initform. This is portable but creates ugly client code, because the user cannot:

(define-condition concrete-condition (protocol-condition) ())

but instead they have to:

(define-condition concrete-condition (protocol-condition)
  ((%hidden-slot :initarg nil)))

where some-slot is not of any use to the user.

I can work around this as well by providing a custom #'DEFINE-CONDITION calling #'CL:DEFINE-CONDITION, which only adds to the ugliness because now client code utilizing my library has to (:SHADOWING-IMPORT-FROM #:MY-PACKAGE #:DEFINE-CONDITION) in every package where they use MY-PACKAGE along with COMMON-LISP, and the useless slot is nonetheless visible for inspection.

I wouldn't have submitted this ticket if I wouldn't have run out of options I consider sane.

Revision history for this message
Michał "phoe" Herda (phoe-krk) wrote :

s/some-slot/%hidden-slot/

Revision history for this message
Michał "phoe" Herda (phoe-krk) wrote :

...

s/:initarg/:initform/

Revision history for this message
Douglas Katzman (dougk) wrote :

I agree with Stas. The spec is explicitly clear that condition instances are not (necessarily) STANDARD-OBJECT, that SLOT-VALUE might not work, etc.
In addition to affording ourselves various efficiencies in the implementation of conditions, there would be yet another potential issue to solve involving infinite regress.
It's tricky enough to deal with things like unprintable objects in backtraces; I can only imagine how much more tricky if INITIALIZE-INSTANCE protocol has to be fully obeyed to make a condition instance and trying to signal a condition about something going wrong in INITIALIZE-INSTANCE.

Revision history for this message
Douglas Katzman (dougk) wrote :

We have a notion of class freezing. I'd be willing to say that all builtins (WARNING, ERROR, etc) are frozen condition classes (but subclassable) and that calling MAKE-CONDITION on them can bypass MAKE-INSTANCE, etc. Does that help your use-case?

Revision history for this message
Michał "phoe" Herda (phoe-krk) wrote :

My use case actually does not involve calling MAKE-INSTANCE itself or causing MAKE-CONDITION to obey its protocol; I fell prey to the XY problem.

What I actually need is *any* implementation-defined way of defining a function that runs whenever an instance of given condition type is created. This specific use-case does not require that this has any kind of inheritance that CLOS's INITIALIZE-INSTANCE :AFTER has; I simply want to define a condition type FOO, and a function that will run every single time a condition of exact type FOO is instantiated.

Revision history for this message
Mateusz Malisz (gwmaniak) wrote :

I would also like to request such a feature.

I have a base error that is used in other error as a PARENT-TYPE. Using CLOS, i.e. :AFTER method for INITIALIZE-INSTANCE to modify the object's slots would allow me to cleanly and idiomatically express my intentions in the code. Lack of such a feature forces me to think of workarounds, which do not make my code any more readable.

I would also like to kindly disagree with Stas: implementing this feature would help at least two users (me and Michał). Also, there is no problem with the standard. The standard says "make-condition, not make-instance, must be used to create condition objects *explicitly*."(emphasis mine). It does not forbid MAKE-CONDITION to use MAKE-INSTANCE internally (nor forces it). Bottom line: it would conform to the standard, as the precise implementation of conditions is implementation-defined.

We are not saying that SBCL does not conform to the standard. Instead, we are suggesting another way of implementing conditions, that would, too, conform to the standard, but would also allow users to leverage CLOS to improve readability of the code.

Revision history for this message
Ph. Marek (ph-marek) wrote :

I guess this one's here would be solved too:

While

    (defclass foo1 () ((bar)))
    (make-instance 'foo1 :bar 1)

croaks about not knowing about :BAR;
the same with a condition class

    (define-condition foo2 () ((bar)))
    (make-condition 'foo2 :bar 1)

can build instances, just the slot is unbound.

CLtL2 "29.4.6. Creating Conditions" doesn't
mention any intended behaviour for that.

Eg. clisp gives the same error for both cases.

summary: - Change MAKE-CONDITION to call MAKE-INSTANCE
+ MOP protocol/constructors for conditions
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.