CLOS normally requires congruent lambda lists for all methods in a
generic function, but I would like get around that for one of the
projects I am working on. In my code I defined a slot if a couple of
classes, and then later added a virtual accessor to another class with
an optional parameter, which errored because of the automatically
created generic function in the first class.
Thanks in advance for any help that is offered, and apologies for
asking if the problem has already been solved.
Ideally, I would like to have something like the following:
;; Wrapper on SDL surface.
(defclass image ()
((disp :reader disp
:initarg :disp)))
;; Sequence of image objects.
(defclass sprite ()
((images :reader images
:initarg :images)))
;; Error here!!!
(defmethod disp ((sprite sprite) &optional (n 0))
(disp (svref (images sprite) n)))
(let ((a (make-instance 'image :disp 'a))
(b (make-instance 'image :disp 'b))
(c (make-instance 'image :disp 'c)))
(defvar example (make-instance 'sprite :images '(a b c))))
(disp example 1)
; returns 'B
This code fails on CLISP with the following error message:
*** - #<STANDARD-METHOD (#<STANDARD-CLASS SPRITE>)> has 1, but
#<STANDARD-GENERIC-FUNCTION DISP>
has 0 optional parameters
I could fix the problem by defining the reader for image class
explicitly and giving it a fake optional parameter:
(defmethod disp ((image image) &optional (ignore nil))
(slot-value image 'image))
But I would prefer not to do that if possible, because it's just ugly,
I would have to change a number of existing classes, and it would add
a requirement for all future subclasses to also implement this hack.
There has to be a better way to do this. (Or if there isn't, there
should be.) (And hopefully it won't involve using MOP, but if it does,
I guess its better than no solution at all....)
Again thank you for you time.
--
Elliott Slaughter
On Dec 19, 4:00 pm, Elliott Slaughter <················@gmail.com>
wrote:
> CLOS normally requires congruent lambda lists for all methods in a
> generic function, but I would like get around that for one of the
> projects I am working on. In my code I defined a slot if a couple of
> classes, and then later added a virtual accessor to another class with
> an optional parameter, which errored because of the automatically
> created generic function in the first class.
It's not automatically created. You requested it using ``:reader
disp''.
> Thanks in advance for any help that is offered, and apologies for
> asking if the problem has already been solved.
>
> Ideally, I would like to have something like the following:
>
> ;; Wrapper on SDL surface.
> (defclass image ()
> ((disp :reader disp
> :initarg :disp)))
>
> ;; Sequence of image objects.
> (defclass sprite ()
> ((images :reader images
> :initarg :images)))
>
> ;; Error here!!!
> (defmethod disp ((sprite sprite) &optional (n 0))
> (disp (svref (images sprite) n)))
You're trying to use one generic function for two different things,
which is silly.
How about:
(defmethod disp-of-nth-image ((sprite sprite) n)
(disp (svref (images sprite) n)))
But this doesn't buy you much over an NTH-IMAGE accessor over sprites,
which then lets you do:
(disp (nth-image sprite 3))
versus
(disp-of-nth-image sprite 3)
And now of course, if you ever need the slot written, that's taken
care of just by making DISP a full accessor. Then you can do:
(setf (disp (nth-image sprite 3)) ...)
Whereas to do that with DISP-OF-NTH-IMAGE, you have to define a SETF
method for it.
> (let ((a (make-instance 'image :disp 'a))
> (b (make-instance 'image :disp 'b))
> (c (make-instance 'image :disp 'c)))
> (defvar example (make-instance 'sprite :images '(a b c))))
Yuck, what? How is the example instance associated with the three
image instances? You have the names of the lexicals quoted in a list.
Don't you mean:
(make-instance 'sprite :images (list a b c))
?
On Dec 19, 4:29 pm, Kaz Kylheku <········@gmail.com> wrote:
> How about:
>
> (defmethod disp-of-nth-image ((sprite sprite) n)
> (disp (svref (images sprite) n)))
Ugly. Point taken.
> But this doesn't buy you much over an NTH-IMAGE accessor over sprites,
> which then lets you do:
>
> (disp (nth-image sprite 3))
> versus
>
> (disp-of-nth-image sprite 3)
Getting better.
> And now of course, if you ever need the slot written, that's taken
> care of just by making DISP a full accessor. Then you can do:
>
> (setf (disp (nth-image sprite 3)) ...)
Shouldn't need it but thanks anyways.
> Whereas to do that with DISP-OF-NTH-IMAGE, you have to define a SETF
> method for it.
>
> > (let ((a (make-instance 'image :disp 'a))
> > (b (make-instance 'image :disp 'b))
> > (c (make-instance 'image :disp 'c)))
> > (defvar example (make-instance 'sprite :images '(a b c))))
>
> Yuck, what? How is the example instance associated with the three
> image instances? You have the names of the lexicals quoted in a list.
>
> Don't you mean:
>
> (make-instance 'sprite :images (list a b c))
Yes. Sorry about that.
Anyways, I liked your second solution. It's probably what I'll end up
doing.
Thanks for your time.
--
Elliott Slaughter
In article
<····································@b1g2000pra.googlegroups.com>,
Elliott Slaughter <················@gmail.com> wrote:
> CLOS normally requires congruent lambda lists for all methods in a
> generic function, but I would like get around that for one of the
> projects I am working on. In my code I defined a slot if a couple of
> classes, and then later added a virtual accessor to another class with
> an optional parameter, which errored because of the automatically
> created generic function in the first class.
>
> Thanks in advance for any help that is offered, and apologies for
> asking if the problem has already been solved.
>
> Ideally, I would like to have something like the following:
>
> ;; Wrapper on SDL surface.
> (defclass image ()
> ((disp :reader disp
> :initarg :disp)))
>
> ;; Sequence of image objects.
> (defclass sprite ()
> ((images :reader images
> :initarg :images)))
>
> ;; Error here!!!
> (defmethod disp ((sprite sprite) &optional (n 0))
> (disp (svref (images sprite) n)))
>
> (let ((a (make-instance 'image :disp 'a))
> (b (make-instance 'image :disp 'b))
> (c (make-instance 'image :disp 'c)))
> (defvar example (make-instance 'sprite :images '(a b c))))
>
> (disp example 1)
> ; returns 'B
>
> This code fails on CLISP with the following error message:
> *** - #<STANDARD-METHOD (#<STANDARD-CLASS SPRITE>)> has 1, but
> #<STANDARD-GENERIC-FUNCTION DISP>
> has 0 optional parameters
>
> I could fix the problem by defining the reader for image class
> explicitly and giving it a fake optional parameter:
>
> (defmethod disp ((image image) &optional (ignore nil))
> (slot-value image 'image))
>
> But I would prefer not to do that if possible, because it's just ugly,
> I would have to change a number of existing classes, and it would add
> a requirement for all future subclasses to also implement this hack.
> There has to be a better way to do this. (Or if there isn't, there
> should be.) (And hopefully it won't involve using MOP, but if it does,
> I guess its better than no solution at all....)
I'm not sure how you expect this to work. What do you expect to happen
if you do
(disp (make-image) 10)
The reason methods have to be congruent is because of the basic OO
principle that when you're calling the generic function you don't care
what the type of the specialized arguments are -- the dispatching
mechanism handles this for you automatically. If you have to be careful
not to pass the optional argument in some cases, then it's not REALLY
the same generic function.
--
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
*** PLEASE don't copy me on replies, I'll read them in the group ***
On Dec 19, 4:20 pm, Barry Margolin <······@alum.mit.edu> wrote:
> I'm not sure how you expect this to work. What do you expect to happen
> if you do
>
> (disp (make-image) 10)
*** - EVAL: undefined function MAKE-IMAGE
I think you meant
(disp (make-instance 'image) 10)
unless you are depending on some sort of automatic constructor
creation I don't know about. But assuming that worked or was replaced
with what I suggested, then I'd probably expect to see something along
the lines of the following:
*** - EVAL: too many arguments given to DISP: (DISP (MAKE-INSTANCE
'IMAGE) 10)
Because the sprite version of disp doesn't take an optional argument.
> The reason methods have to be congruent is because of the basic OO
> principle that when you're calling the generic function you don't care
> what the type of the specialized arguments are -- the dispatching
> mechanism handles this for you automatically. If you have to be careful
> not to pass the optional argument in some cases, then it's not REALLY
> the same generic function.
Maybe I'm thinking of this too much like the message passing model,
i.e. in Ruby you might do something like the following:
class Image
attr_reader :disp
def initialize(disp = nil)
@disp = disp
end
end
class Sprite
attr_reader :images
def initialize(images = [])
@images = images
end
def disp(n = 0)
images[n].disp
end
end
And when you call disp on an image with an argument you get an error:
Image.new.disp(10)
ArgumentError: wrong number of arguments (1 for 0)
from (irb):24:in `disp'
from (irb):24
I guess I'm still coming to grips with the full implications of
generics. While specialization on multiple parameters is good, I don't
really see why it has to keep you from doing stuff like the above.
Thanks for you comments anyways.
Elliott Slaughter wrote:
> There has to be a better way to do this. (Or if there isn't, there
> should be.) (And hopefully it won't involve using MOP, but if it does,
> I guess its better than no solution at all....)
I haven't read the other part of your posting in detail, but just as a
note: No, it's not possible to do this using the CLOS MOP. The CLOS MOP
specifies the runtime behavior of CLOS, and as such it could actually be
made to work. However, ANSI Common Lisp allows CL compilers to check
method congruency already at compile time, independent of what the
involved generic function classes or the method classes are, and the
CLOS MOP doesn't provide any means to switch off such compile-time
checks. Many CL implementations indeed perform such compile-time checks,
so no luck here.
This is a pity, but that's like it is.
Pascal
--
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
Pascal Costanza wrote:
> I haven't read the other part of your posting in detail, but just as a
> note: No, it's not possible to do this using the CLOS MOP. The CLOS MOP
> specifies the runtime behavior of CLOS, and as such it could actually be
> made to work. However, ANSI Common Lisp allows CL compilers to check
> method congruency already at compile time, independent of what the
> involved generic function classes or the method classes are, and the
> CLOS MOP doesn't provide any means to switch off such compile-time
> checks.
Pascal -- You seem rather confident in this statement. I don't have any
argument that it is _not_ so, but I can't think of anything in the ANS
or AMOP that would permit such a check at compile/compile-file time.
Perhaps a compilation warning, but so far as I can see there is no error
in compiling a noncongruent method definition -- only in executing one.
I'm not sure it is strictly impossible to implement a gf that accepts
noncongruent lambda lists using the MOP. I haven't thought through all
the details, but if possible it would require overriding nearly all
the gf subprotocols. It would be fun to try to implement this, but I
have better things to do with the rest of my life.
IIRC the real reason that noncongruent lambda lists were prohibited from
standard gfs was twofold. First, the CLOS designers desired to make it
possible to implement CLOS with enough efficiency that it would actually
be usable. Second, without congruent lambda lists (at least in the
number of required parameters) would require a much more complex
mechanism to control argument-precedence order in sorting the applicable
methods. This is what has been cited as the reason in the past...
Steven M. Haflich wrote:
> Pascal Costanza wrote:
>
>> I haven't read the other part of your posting in detail, but just as a
>> note: No, it's not possible to do this using the CLOS MOP. The CLOS
>> MOP specifies the runtime behavior of CLOS, and as such it could
>> actually be made to work. However, ANSI Common Lisp allows CL
>> compilers to check method congruency already at compile time,
>> independent of what the involved generic function classes or the
>> method classes are, and the CLOS MOP doesn't provide any means to
>> switch off such compile-time checks.
>
> Pascal -- You seem rather confident in this statement. I don't have any
> argument that it is _not_ so, but I can't think of anything in the ANS
> or AMOP that would permit such a check at compile/compile-file time.
> Perhaps a compilation warning, but so far as I can see there is no error
> in compiling a noncongruent method definition -- only in executing one.
It's a side effect of several places in the HyperSpec. The "Exceptional
Situations" entries for defgeneric and defmethod state that method
lambda lists must be congruent to the respective generic function lambda
list, or otherwise an error is signaled (which indeed may only trigger a
warning at compile time, but read on...)
One possible way to deviate from lambda list congruency is by having too
many or too few arguments (and that covers the OP's wish, if I
understand it correctly). However, Section 3.5.1 about "Argument
Mismatch Detection" specifies that a compiler is allowed to signal an
error at compile time if you pass too many or too few arguments to a
function. If a defgeneric form and a corresponding defmethod form
disagree in the number of arguments they accept, _and_ you have calls to
that generic function in your code, you can't get it compiled under
these circumstances if a CL implementation chooses do indeed issue an
error compile time in these cases.
> I'm not sure it is strictly impossible to implement a gf that accepts
> noncongruent lambda lists using the MOP. I haven't thought through all
> the details, but if possible it would require overriding nearly all
> the gf subprotocols. It would be fun to try to implement this, but I
> have better things to do with the rest of my life.
This could simply be up to the discriminating function. Indeed, it would
have been an option for me to implement ContextL by adding the implicit
parameter that is required to dispatch on conext in the discriminating
function, very roughly like this:
(defmethod compute-discriminating-function ((gf context-function))
(let ((df (call-next-method)))
(lambda (&rest args)
(apply df *context* args))))
Alas, because of the restrictions stated above, this is not possible in
CLOS.
An easy fix could be to just drop the compile time checks in case the
:generic-function-class option is set to something other than
standard-generic-function, or by adding a :do-not-check-call-sites to
the defgeneric form. However, I haven't thought through all the details
here either.
Having said that, let me stress that I found a different solution for
ContextL, by separating calling functions from defining functions. It's
not as elegant as changing the discriminating function, but it's good
enough for practical purposes.
> IIRC the real reason that noncongruent lambda lists were prohibited from
> standard gfs was twofold. First, the CLOS designers desired to make it
> possible to implement CLOS with enough efficiency that it would actually
> be usable. Second, without congruent lambda lists (at least in the
> number of required parameters) would require a much more complex
> mechanism to control argument-precedence order in sorting the applicable
> methods. This is what has been cited as the reason in the past...
Who do the CLOS designers think they are to know what we users want to
do with the CLOS MOP?!? ;-) ;-) ;-)
Pascal
--
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
Elliott Slaughter wrote:
>
> I could fix the problem by defining the reader for image class
> explicitly and giving it a fake optional parameter:
>
> (defmethod disp ((image image) &optional (ignore nil))
> (slot-value image 'image))
>
> But I would prefer not to do that if possible, because it's just ugly,
> I would have to change a number of existing classes, and it would add
> a requirement for all future subclasses to also implement this hack.
> There has to be a better way to do this. (Or if there isn't, there
> should be.) (And hopefully it won't involve using MOP, but if it does,
> I guess its better than no solution at all....)
I think this is what you should do.
The :reader clause is just a convenience for defining a
generic function and a method that happens to fit a
particular common pattern. Your needs not hot happen
to fit that pattern, so you have to do it manually.
All methods for one generic function should have the same
parameters, because the caller of the generic function
should not need to know that the function is generic.
Think about how you would write the doc string for
the defgeneric for this function, if you were to write
an explicit defgeneric. What is the contract between
this function and its caller? The contract must be
that it is legal to pass two arguments. So you need
a definition of the disp generic function for the image
class that does indeed accept two arguments.
If you're doing this kind of thing a lot, you might
consider defining a macro.
-- Dan