From: Elliott Slaughter
Subject: Non-congruent Lambda Lists in CLOS
Date: 
Message-ID: <7d79bafe-38be-43dd-a849-da4158114ee3@b1g2000pra.googlegroups.com>
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

From: Kaz Kylheku
Subject: Re: Non-congruent Lambda Lists in CLOS
Date: 
Message-ID: <e64e869d-fd5d-4f88-bdab-a0a38fff5d21@b40g2000prf.googlegroups.com>
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))

?
From: Elliott Slaughter
Subject: Re: Non-congruent Lambda Lists in CLOS
Date: 
Message-ID: <ee2c7a5a-2bb9-44f0-94a4-7ad6e21e2a20@b1g2000pra.googlegroups.com>
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
From: Barry Margolin
Subject: Re: Non-congruent Lambda Lists in CLOS
Date: 
Message-ID: <barmar-B1C552.19202119122007@comcast.dca.giganews.com>
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 ***
From: Elliott Slaughter
Subject: Re: Non-congruent Lambda Lists in CLOS
Date: 
Message-ID: <c29b413b-5197-45b4-b7e6-abc1d0500cd3@a35g2000prf.googlegroups.com>
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.
From: Pascal Costanza
Subject: Re: Non-congruent Lambda Lists in CLOS
Date: 
Message-ID: <5sulnlF1a17hhU1@mid.individual.net>
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/
From: Steven M. Haflich
Subject: Re: Non-congruent Lambda Lists in CLOS
Date: 
Message-ID: <UV1dj.27344$4V6.26559@newssvr14.news.prodigy.net>
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...
From: Pascal Costanza
Subject: Re: Non-congruent Lambda Lists in CLOS
Date: 
Message-ID: <5tnv0mF1epn2eU1@mid.individual.net>
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/
From: Daniel Weinreb
Subject: Re: Non-congruent Lambda Lists in CLOS
Date: 
Message-ID: <48429FE0.6020902@alum.mit.edu>
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