Re: Generics vs. Message passing: object namespaces
Dave Roberts <[email protected]>
| Newsgroups | gmane.comp.lang.lightweight |
|---|---|
| Message-ID | <[email protected]> |
On Thu, 2007-02-22 at 09:26 +0000, Tom Locke wrote: > To my mind one of the chief benefits of the OO style is in > organisation of code. We put things in places that makes sense, and > we find them easily later. Yes this breaks down if a method belongs > equally in two places, but it's still a big net win IMO. I used to think this as well. When I first encountered CLOS, coming from a standard C++ -> Java background, it seemed downright strange. I was so steeped in the "invoke a method on an object" model that I just couldn't wrap my head around generic functions. In fact, I remember that when I first learned OOP I was told that one of the benefits of OOP was that it bound a data structure and the code to manipulate it together. Unlearning this was difficult at first. Having worked with CLOS more now, I really appreciate the CLOS model. I found that the key was thinking of method invocations not as message sending, or even invoking something "on an object", but rather dispatching based on some information, some of which may be type information. In my CLOS "mental model" I just imagine a little trampoline doing something the equivalent of: (defun generic-foo (arg1 arg2) (typecase arg1 (type1 (generic-foo-method1 arg1 arg2) (type2 (generic-foo-method2 arg1 arg2))) Yes, I know GF method dispatch is far more complex, but that's basically the common case. The nice thing about it is that I don't have to write or manage that dispatch table directly. I can do it declaratively, which makes code far more modular. Simply load a file and new methods appear for that generic function. Those new methods can be in different modules anywhere. I don't have to have the source code to any of the other methods. What you finally realize about CLOS is that it's good for more than just OOP in the classical sense. In addition to method dispatch based on object type, ala (generic-foo object param1 param2) you can also use EQL specializers to create modular dispatch tables. Now, whenever I find myself wanting to write (defun func (arg1 arg2) (ecase arg1 (foo (...)) (bar (...)))) instead I know write (defmethod func ((arg1 (eql 'foo)) arg2) ...) (defmethod func ((arg1 (eql 'bar)) arg2) ...) The nice thing about this is that different modules can all contribute to the func dispatch table, simply by executing a (defmethod func ((arg1 (eql 'blah)) arg2) ...) form. I didn't appreciate this aspect of CLOS method dispatch until I read Peter Siebel's Practical Common Lisp. Peter makes extensive use of this technique and I have come to appreciate it a lot. In summary, remember the word "dispatch" and you'll be better for it when trying to wrap your head around CLOS. > In message-passing style OO, it's often possible to simply guess the > method name, and there is often support in the editor for enumerating > the available methods. So writing is easier. Reading is easier too - > that "_to_buffer" in the above example is largely noise. Noise that > adds up pretty quickly. To a certain extent this is just editor mechanics. It's not quite as easy as with the message-send model in that it's harder to deduce the type of object you're trying to invoke a method on before you have written down the object. In other words, with: object.| vs. (| where "|" is the carat location, the editor can be smarter about whittling down the set of applicable methods based on the type of object. But the editor can obviously still help you out with names in the CLOS example, too, and SLIME will do it if you just start typing something (anything). -- Dave