Guile Mailing List Archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
>>>>> "Per" == Per Bothner <email@example.com> writes:
Per> I understand how to model single-dispatch in a state-less
Per> manner: An object has a bunch of fields and methods
Per> associated with it. The association is created when the
Per> object is created - i.e. there is no need for side-effects
Per> to understand or implement this. Calling a method is just a
Per> matter of selecting the right method from the object using a
Per> key given by the call. I can add a new class to a running
Per> system without changing any of the objects (classes or
Per> instances) already existing.
Ah, I see. OK, here's a way to do generic functions *and*
multi-dispatch in a functional setting. It doesn't do what *I* want
it to do, because I have to specify all methods in one place, but it
does support a side-effect free model for method invocation. Note
that CLOS proper doesn't do the :from stuff I describe when I deal
with inheritance IIRC, and so this may actually be a little bit more
A class is now just a bag of slots and `forwarding pointers' or the
A generic function is a dispatch mechanism with a set number of
(define-generic whatever (lambda list)
(a lambda list) => (code)
(a lambda list) => (code)
(define-generic whatever (lambda list)
(define-method (a lambda list)
(define-method (another lambda list)
Think of a table if you like, filled out at define-generic time. No
side effects so far.
When I go (whatever foo bar), whatever looks up the classes of foo and
bar, performs some magic to get a method out of both of them, and
invokes the method. All side effect free.
A downside to this is as it stands is that it does not support
incremental inheritance well. We could extend it to get a limited
form of incrementality: for each bag of classes (basically for each
module), after the classes are defined you do a
(define-generic whatever :from (foo:whatever bar:whatever)
(define-method (yet another lambda list)
that augments the dispatch table with the new methods without
modifying the old one. This does not modify either of the original
whatevers, and enables methods that want to use a generic function on
new classes to do so.
Now, this isn't terribly satisfying to me, because it restricts us: we
can only define methods in one place. To support defining methods in
other places, we can add a nonfunctional way of futzing with the
(define-method! whatever (a lambda list)
goes into whatever's dispatch table, and munges it to support (a lambda
list), i.e. it modifies whatever's dispatch table (and of course only
whatever's dispatch table, not those of its parent or whatever).
Does this satisfy you? I can certainly deal with it as a conceptual
model, and as described in my previous message the MOP should be easy
enough to make functional (of course, when we're not dealing with
setting slots or the like).
Per> Perhaps it is possible to keep these properties in a language
Per> with multi-dispatch, but I don't know how. I'd love for
Per> someone to point me at how to do it. (Does Cecil
Per> multi-dispatch coms closer?)
This is all coming out of my slightly hazy brain at the moment, but
IIRC Cecil is a *very* procedural language.
Per> That is not the issue. The issue is access to the methods,
Per> not the generic functions. I am concerned that one can add
Per> new methods to a running system and have the behavior of
Per> calls change, even when all the parameters involved predate
Per> the new methods. You might think this is a feature; I don't.
Per> (Of course being able to modify a running system is useful,
Per> but there is an important distinction between adding to a
Per> system vs modifying existing code in it.)
You should be able to add new methods to a running system that don't
affect the existing ones and add new methods to a running system that
do affect the existing ones with this model, so modifying existing
code and adding should both Just Work.
Per> Flexibility is fine; the question is: does it allow you to
Per> write better programs (shorter, more efficient, more
Per> maintainable, more correct)? If not, then the cost is major.
Per> (My main concern is not implementation efficiency, let me
Well, yes, actually. Here's an industrial application of multimethods
and why you wouldn't want to do it with the Visitor pattern, the usual
way of simulating multiple dispatch in a single dispatch language.
Some background: CLIM is the Common Lisp Interface Manager and is the
canonical way to do interfaces for commercial CL programs. There's no
free version as yet, but Harlequin is (or at least was) good enough to
put their reference manual online.
CLIM is built around a stream model; data comes in an input stream and
goes out an output stream. To ask the user a question, for example
`Really rm -rf /? You'll be sorry...' you send a question object to
the output stream and await a response from the input stream.
In a GUI, this might be rendered as a modal dialog. But CLIM supports
more than just GUIs: you can make streams that do ncurses like stuff,
you can do a command line, you can embed a command line in your GUI
and the program won't know the difference, you can even write a CLIM
stream that does internet protocols like POP3. CL-HTTP has CLIM
streams that render HTML on the way out and parse <FORM> nonsense on
the way in.
To render themselves on a stream, objects use the present function.
(present object :stream stream)
which goes very quickly to this method:
(stream-present stream object ...)
Now, if I write a new stream type (for example, SMTP-like interactions
on a network stream), I need to have new methods for
(stream-present (stream my-new-stream) object ...)
(and probably special ones for my-new-stream and the fundamental
But, if I define a new presentation object, I need to have new methods
(stream-present stream (object my-new-object) ...)
So I now have two separate heirarchies of objects that are evolving
independently and yet still need to work together. You *cannot* do
this with Visitor pattern; it requires one or the other to be fixed.
Worse, you can't tell incrementally tell the Visitor object that
rendering a rectangle on a SMTP stream is too weird for me to deal
with; you have to redo the whole Visitor thing. *AND* if I make a
subclass of my-new-stream to specially handle, say, my-new-object, I
have to redo everybody's visitor stuff to reflect that a new stream
exists but everybody except my-new-object has to treat him like his
So this is my example of where multimethods are good and make things
Per> Note, I see separate compilation as very important. The
Per> operation of linking should be one of combining different
Per> "modules"; if linking requires re-arranging method precence
Per> tables at the drop of a hat make me very nervous. This is
Per> related to the goal static analysis; if you can only make
Per> meaningful statements about whole programs, but not modules,
Per> then I don't think it is very interesting.
This should manage separate compilation.
Graham Hughes <firstname.lastname@example.org>
PGP Fingerprint: 36 15 AD 83 6D 2F D8 DE EC 87 86 8A A2 79 E7 E6
Guile Home |
Main Index |