5

I'm exploring some possibilities of Common Lisp syntax and I wanted to make an :around method on make-instance to return some arbitrary value in some cases. For sake of simplicity, let it be nil, when I don't pass a needed argument. And it works, but not when calling in let:

(defclass foo ()
  ((bar :initarg := :initform '())))

(defmethod make-instance :around ((type (eql 'foo)) &key =)
  (if (not =) nil (call-next-method)))

(print (make-instance 'foo))    ;; => NIL

(print (let ((x (make-instance 'foo))) x)) ;; => #<FOO {10037EEDF3}> 

Can somebody explain this situation? Why is that so? Does SBCL try to be smart or is it actually a standard thing to be done? I know that I can work around it by using apply:

(print (let ((x (apply #'make-instance (list 'foo)))) x)) ;; => NIL

But I don't want to rely on this sort of workaround. Actually I can use a regular function for that and it's OK, but I want to understand why it works this way and if this behaviour can be disabled.

Boann
  • 48,794
  • 16
  • 117
  • 146
Bartek Lew
  • 101
  • 5

2 Answers2

4

Looks like one of the optimization attempts for MAKE-INSTANCE and constant class names in SBCL (-> CTOR)...

This seems to work:

(defmethod make-instance :around ((class (eql (find-class 'foo)))
                                  &rest initargs
                                  &key =
                                  &allow-other-keys)
  (declare (ignorable initargs))
  (if (not =) nil (call-next-method)))

But probably useful to ask a SBCL expert or file a bug report...

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • Thanks, actually `(class (eql (find-class 'foo)))` is enough to make it work, but good to know those other things you used here. – Bartek Lew Jun 16 '19 at 11:00
0

Your program is non-conforming, so any result you observe might change from one implementation to another, or from one version of an implementation to another.

See Constraints on the COMMON-LISP Package for Conforming Programs, in particular clause 19:

Defining a method for a standardized generic function which is applicable when all of the arguments are direct instances of standardized classes.

The meta-object protocol imposes restrictions on portable programs:

Any method defined by a portable program on a specified generic function must have at least one specializer that is neither a specified class nor an eql specializer whose associated value is an instance of a specified class.

In your case, the standardized generic function is MAKE-INSTANCE, and the associated value of the eql specializer is foo, an instance of the symbol class.

In the (eql (find-class 'foo)) method, the associated value is a direct instance of standard-class, so it is non-conforming too; you should use a custom metaclass to define new methods on make-instance.

Furthermore, the :around method returning nil is another problem:

Portable programs may define methods that extend specified methods unless the description of the specified method explicitly prohibits this. Unless there is a specific statement to the contrary, these extending methods must return whatever value was returned by the call to call-next-method.

The hyperspec says that make-instance must return a fresh instance of the given class, where instance is defined as either a direct instance or an indirect instance. The glossary entry for direct instance has an example sentence that says that make-instance always return a direct instance of a class (glossary entries are normative (here this is in an example sentence, so there might be room for interpretation)). Returning NIL, however, is non-conforming.

Thanks to SBCL developers for their time; see https://bugs.launchpad.net/sbcl/+bug/1835306

coredump
  • 37,664
  • 5
  • 43
  • 77