Почему вызов `make-instance` в` let` работает иначе? - PullRequest
5 голосов
/ 16 июня 2019

Я изучаю некоторые возможности синтаксиса Common Lisp и хотел сделать метод :around для make-instance, чтобы в некоторых случаях возвращать произвольное значение.Ради простоты, пусть это будет nil, когда я не передам необходимый аргумент.И это работает, но не при звонке в 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}> 

Может кто-нибудь объяснить эту ситуацию?Почему это так?Пытается ли SBCL быть умным или это действительно стандартная вещь?Я знаю, что могу обойти это, используя apply:

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

Но я не хочу полагаться на этот способ обхода.На самом деле я могу использовать обычную функцию для этого, и это нормально, но я хочу понять, почему она работает таким образом и можно ли отключить это поведение.

Ответы [ 2 ]

4 голосов
/ 16 июня 2019

Похоже, что одна из попыток оптимизации для MAKE-INSTANCE и имен констант в SBCL (-> CTOR) ...

Это похоже на работу:

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

Но, вероятно,полезно спросить эксперта SBCL или отправить отчет об ошибке ...

0 голосов
/ 03 июля 2019

Ваша программа не соответствует требованиям, поэтому любой наблюдаемый вами результат может измениться от одной реализации к другой или от одной версии реализации к другой.

См. Ограничения в пакете COMMON-LISPдля соответствующих программ , в частности пункт 19:

Определение метода для стандартизированной обобщенной функции, которая применима, когда все аргументы являются прямыми экземплярами стандартизированных классов.

Протокол мета-объектов налагает ограничения на переносимые программы :

Любой метод, определенный переносимой программой в указанной универсальной функции, должен иметь хотя бы один специализатор, который не является ниуказанный класс, ни специализатор eql, чье ассоциированное значение является экземпляром указанного класса.

В вашем случае стандартизированная обобщенная функция имеет вид MAKE-INSTANCE и соответствующее значениеспециализации eql - foo, экземпляр класса symbol.

В (eql (find-class 'foo)) mнапример, ассоциированное значение является прямым экземпляром standard-class, поэтому оно также не соответствует;Вы должны использовать пользовательский метакласс для определения новых методов в make-instance.

Кроме того, метод :around, возвращающий nil, является еще одной проблемой:

Переносимые программы могут определять методыкоторые расширяют указанные методы, если описание указанного метода явно не запрещает это.Если нет специального утверждения об обратном, эти расширяющие методы должны возвращать любое значение, которое было возвращено вызовом call-next-method.

Гиперспец говорит, что make-instance должен возвращать новый экземпляр данного класса , где экземпляр определяется как прямой экземпляр или косвенныйэкземпляр .Запись глоссария для прямого экземпляра содержит пример предложения, в котором говорится, что make-instance всегда возвращает прямой экземпляр класса (записи глоссария являются нормативными (здесь этов предложении примера, так что может быть место для интерпретации)).Однако возврат NIL не соответствует требованиям.

Спасибо разработчикам SBCL за их время;см https://bugs.launchpad.net/sbcl/+bug/1835306

...