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] |
>>>>> "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