Несоответствие аргументов макроса Common Lisp, несмотря на &rest / &body

Я читал книгу Питера Сейбеля Practical Common Lisp, собирая проект из кода книги, доступного в Интернете. в том порядке, в котором он указан в книге, и до сих пор у меня есть файл, который компилирует и загружает код каждой главы по очереди, и именно здесь я столкнулся с проблемами: когда я пока загружаю FASL для проекта, я получаю предупреждения в разделе ID3v2, подобные приведенному ниже.

Я не понимаю, где возникает конфликт номеров аргументов. UNSIGNED-INTEGER, похоже, получает два аргумента ключевого слова. Также мне кажется, что макрос DEFINE-BINARY-TYPE примет любое количество аргументов с использованием &rest/&body. Мне было интересно, есть ли у вас какие-либо намеки или советы. Некоторые соответствующие выходные данные и код приведены ниже. Любая помощь приветствуется.

Заранее спасибо,

; file: .../cl-playlist/id3v2.lisp
; in: DEFINE-BINARY-TYPE U1
;     (BINARY-DATA:DEFINE-BINARY-TYPE ID3V2::U1
;         NIL
;       (ID3V2::UNSIGNED-INTEGER :BYTES 1 :BITS-PER-BYTE 8))
; ...
; ==>
;   (BINARY-DATA:READ-VALUE 'ID3V2::UNSIGNED-INTEGER #:STREAM :BYTES 1
;                           :BITS-PER-BYTE 8)
; 
; caught STYLE-WARNING:
;   The function was called with six arguments, but wants exactly two.

Оскорбительная функция из "id3v2.lisp" выглядит так:

(define-binary-type u1 () (unsigned-integer :bytes 1 :bits-per-byte 8))

с использованием

(define-binary-type unsigned-integer (bytes bits-per-byte)
  (:reader (in)
       (loop with value = 0
          for low-bit 
          downfrom (* bits-per-byte (1- bytes)) to 0 by bits-per-byte do
        (setf (ldb (byte bits-per-byte low-bit) value) (read-byte in))
          finally (return value)))
  (:writer (out value)
       (loop for low-bit 
          downfrom (* bits-per-byte (1- bytes)) to 0 by bits-per-byte
          do (write-byte (ldb (byte bits-per-byte low-bit) value) out))))

из следующего в "binary-data.lisp"

(defmacro define-binary-type (name (&rest args) &body spec)
; (defmacro define-binary-type (name &rest args &body spec)
  (with-gensyms (type stream value)
  `(progn
    (defmethod read-value ((,type (eql ',name)) ,stream &key ,@args)
      (declare (ignorable ,@args))
      ,(type-reader-body spec stream))
    (defmethod write-value ((,type (eql ',name)) ,stream ,value &key ,@args)
      (declare (ignorable ,@args))
      ,(type-writer-body spec stream value)))))

person SquareCrow    schedule 07.04.2013    source источник


Ответы (1)


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

Видеть это:

CL-USER> (defmethod foo ((a string) (b string) &key) (list a b))
STYLE-WARNING:
   Implicitly creating new generic function COMMON-LISP-USER::FOO.
#<STANDARD-METHOD FOO (STRING STRING) {1005603C53}>

Выше сказано, что DEFMETHOD также создает соответствующую универсальную функцию, потому что ее не было. Это нормально. Мы также могли бы использовать DEFGENERIC для объявления универсальной функции и ее аргументов. Здесь SBCL выводит это из метода, который он видит.

Метод имеет ровно два аргумента. Аргументы ключевых слов отсутствуют. Давайте вызовем ее из другой функции с несколькими, предположительно, ключевыми аргументами.

CL-USER> (defun bar (baz) (foo baz baz :k1 10))
; in: DEFUN BAR
;     (FOO BAZ BAZ :K1 10)
;
; caught STYLE-WARNING:
;   The function was called with four arguments, but wants exactly two.
;
; compilation unit finished
;   caught 1 STYLE-WARNING condition
BAR
CL-USER> 

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

В вашем случае объявление U1 описывает функцию с двумя аргументами. Для READ-DATA-VALUE нет аргументов ключевого слова.

Теперь есть несколько возможных способов справиться с этим:

  1. используйте DEFGENERIC, чтобы определить общую функцию READ-DATA-VALUE со списком аргументов, который вы действительно хотите использовать, и убедитесь, что все ваши методы следуют ему.

  2. Поместите все аргументы во все методы. В методах, где они не используются, объявляйте их игнорируемыми.

  3. разрешить другие аргументы ключевого слова, &allow-other-keys, чтобы разрешить различные методы иметь разные наборы аргументов ключевого слова. Лучше всего сделать это также в форме DEFGENERIC.

Потом:

CL-USER> (defmethod foo1 ((a string) (b string) &key &allow-other-keys)
           (list a b))
STYLE-WARNING:                                                                                                                                                                                   
   Implicitly creating new generic function COMMON-LISP-USER::FOO1.                                                                                                                              
#<STANDARD-METHOD FOO1 (STRING STRING) {10058AC193}>                                                                                                                                             
CL-USER> (defun bar1 (baz) (foo1 baz baz :k1 10))
BAR1                                                                                                                                                                                             

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

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

person Rainer Joswig    schedule 07.04.2013