I have been reading Peter Seibel's book, Practical Common Lisp, piecing together the project from the book code available online in the order it appears in the book, and so far, I have a file that compiles and loads each chapter's code in turn, and this is where I have run into problems: when I load the FASL for the project so far, I get warnings in the ID3v2 section like this the one below.
I don't understand where the argument number conflict emerges. UNSIGNED-INTEGER
seems to be getting its two keyword arguments. Also, it seems to me that the DEFINE-BINARY-TYPE
macro will accept any number of arguments
with the use of &rest
/&body
. I was wondering if you had any hints or advice. Some relevant output and code is below.
Any and all help is appreciated.
Thanks in Advance,
; 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.
The offending function from "id3v2.lisp" looks like this,
(define-binary-type u1 () (unsigned-integer :bytes 1 :bits-per-byte 8))
using
(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))))
from the following in "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)))))
The problem your code has is that you call a function with a wrong number of arguments. The function has been created with an argument list of fewer elements.
See this:
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}>
Above says that DEFMETHOD
also creates the corresponding generic function, because there was none. That's fine. We could also use DEFGENERIC
to declare the generic function and its arguments. Here SBCL infers it from the method it sees.
The method has exactly two arguments. There are no keyword arguments. Let's call it from another function with a few, supposedly, keyword arguments.
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>
Now SBCL tells us that we call the generic function with four arguments, even though the generic function has only two parameters.
In your case the declaration of U1
describes a function with two arguments. There are no keyword arguments for READ-DATA-VALUE
.
There are now several possible ways to deal with this:
use a DEFGENERIC
to define the generic function READ-DATA-VALUE
with the argument list you really want to use and make sure all your methods follow it.
Put all the arguments in all methods. In methods where they are not used, declare them to be ignorable.
allow other keyword arguments, &allow-other-keys, to allow different methods to have different sets of keyword arguments. Best do it also in the DEFGENERIC
form.
Then:
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
You can see that SBCL no longer complains and assumes that the arguments are keyword arguments.
The drawback is that the Lisp compiler now assumes that you can use arbitrary keyword arguments for this generic function and can't tell you (also not at compile time) if you pass a wrong one.