[plt-scheme] swindle generic functions top-level and scope
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!