This is the mail archive of the guile@cygnus.com mailing list for the guile project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Re: setf.scm


>>>>> "Per" == Per Bothner <bothner@cygnus.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
complete.

A class is now just a bag of slots and `forwarding pointers' or the
like.

A generic function is a dispatch mechanism with a set number of
methods: basically

	(define-generic whatever (lambda list)
		(a lambda list) => (code)
		(a lambda list) => (code)
		...)

or better

	(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
dispatch table:

	(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).

<snip>

    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
    Per> emphasize.)

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
rendering objects).

But, if I define a new presentation object, I need to have new methods
for

(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
superclass.

So this is my example of where multimethods are good and make things
clearer.

    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 <ghughes@cs.ucsb.edu>
PGP Fingerprint: 36 15 AD 83 6D 2F D8 DE  EC 87 86 8A A2 79 E7 E6