[plt-scheme] swindle generic functions top-level and scope

From: Eli Barzilay (eli at barzilay.org)
Date: Sun Apr 22 23:06:50 EDT 2007

On Apr 21, Brad wrote:
> I did some experiments with swindle. I don't care for some of the
> behavior. Is it possible to change it with the MOP?

The stuff that you're doing below is unrelated to the MOP.


The main problem that you're running against is that `defmethod' is
strange.  Sometimes it defines a new generic with a method that
populates it (ie, a `defgeneric' + `add-method'), and sometimes it's
adding a method to an existing generic.  To avoid confusion, the best
thing you can do is keep all generics at the top-level, and use
`add-method' in nested scopes.  Local generic definitions are
problematic and you probably don't want them anyway: the cost of
creating a generic is much more than creating a closure (which can
often be lifted so there is no cost).

> (defclass <foo> ())
> (defmethod (bar (self <foo>))
>   (printf "bar~n"))
> 
> (define foo-1 (make <foo>)) ; this is the control foo.
> (define foo-2 (make <foo>)) ; this is the guinea pig foo.
> 
> (bar foo-1) ;; -> bar
> 
> (let ()
>   ;change a method
>   (defmethod (bar (self <foo>))
>     (printf "noo bar~n"))
>   (bar (make <foo>))  ;; -> noo bar
>   (bar foo-1)         ;; -> noo bar
>   ;export to top level and see if anything changes.
>   (set! foo-2 (make <foo>)))

Because this `defmethod' appears in a new context, Swindle is guessing
that you want a new generic defined.  You can see this by doing this:

  (define bar*
    (let () (defmethod (bar (self <foo>)) 123) bar))
  (eq? bar bar*) ; -> #f

As you can see, this is a questionable behavior -- but the real
problem is the confusion that `defmethod' involves.


> ; did anything change?
> (bar foo-1)       ;; -> bar (didn't change)
> (bar foo-2)       ;; -> bar (didn't change)
> ; Maybe this is a Good Thing -- I don't know.
> ; Can change methods locally.

These are still using the same generic that was not modified.


> (defclass <foo> ())
> (defmethod (bar (self <foo>))
>   (printf "noo bar noo foo~n"))
> (define foo-2 (make <foo>))
> 
> (bar foo-1)   ;; -> bar (an origial foo -- can't make these anymore)
> (bar foo-2)   ;; -> noo bar noo foo (this is a noo foo)

This is another problem, not specific to Swindle, or even to
MzScheme.  You've created a new class that has the same name, and
added a method with the new class as a specializer to `bar'.  The
thing is that `foo-1' is still an instance of the old `foo'.  Common
Lisp is trying to "sort of" solve this by making the second class
definition modify the class object, so all instances still maintain a
link to the same class and will change themselves when used again.
The moral of this is that you should be careful with re-defining
classes -- it's best to throw away all old instances.  You can see the
same problem with plain closures:

  (define (make-foo x) (lambda (msg) (case msg [(x) x])))
  (define foo-1 (make-foo "foo-1"))
  (foo-1 'x) ; -> "foo-1"
  (define (make-foo x) (lambda (msg) (case msg [(x) `(x = ,x)])))
  (define foo-2 (make-foo "foo-2"))
  (foo-2 'x) ; -> (x = "foo-2")
  (foo-1 'x) ; -> "foo-1"  <-- did not change


> (eq? (class-of foo-1) (class-of foo-2)) ;; -> #f
> (printf "~a~n" (generic-methods bar))
>   ;; -> (#<method:bar:<foo>> #<method:bar:<foo>>)
> ; This is a Good Thing -- doesn't change the class
> ; of an object or it's methods.
> 
> (let ()
>   (bar foo-1)   ;; -> bar
>   (bar foo-2))  ;; -> noo bar noo foo
> ; everything works here.
> 
> (let ()
>   (defclass <inner-foo> ())

(Also, local class definitions are probably too expensive to be
useful.)

>   (defmethod (bar (self <inner-foo>))
>     (printf "bar inner foo ~n"))
>   (set! foo-2 (make <inner-foo>))
>   (bar foo-2)  ;; -> bar inner foo
>   ;(bar foo-1) ;; -> error bar: no applicable primary methods...
>                ;;    (where is my bar?)
>   (printf "~a~n" (generic-methods bar)))
>     ;; (#<method:bar:<inner-foo>>) not visible here?
> ; This is not a Good Thing.

This is the same reason as the above.  You're talking about a
completely new `bar' method.  Better use `add-method' in this case to
modify the global class -- but note that every time this code runs you
get a different `<inner-foo>':

  (define (blah)
    (defclass <inner> ())
    (add-method bar (method ([x <inner>]) (printf "inner bar\n")))
    (bar (make <inner>)))
  (blah)
  (blah)
  (blah)
  (generic-methods bar)
    ; -> (#<method:bar:<inner>> #<method:bar:<inner>> #<method:bar:<inner>> #<method:bar:<foo>>)


> ;(bar foo-2) ;; -> error bar: no applicable primary methods...
> ; I don't know if this is a Good Thing.

You're using the old `bar' now, which never got a method for the inner
class.

-- 
          ((lambda (x) (x x)) (lambda (x) (x x)))          Eli Barzilay:
                  http://www.barzilay.org/                 Maze is Life!


Posted on the users mailing list.