From: Robert Klemme Date: 2004-10-18T16:59:23+09:00 Subject: Re: [Q] case based on object class Markus, thanks for taking the time for the lengthy explanation. Although I have one or two open questions, it seems to me that we agree very much on this and previous misunderstandings were probably due to lack of complete understanding. "Markus" schrieb im Newsbeitrag news:1097906211.21256.399.camel@lapdog.reality.com... > > > > I don't think so. Name clashes and building proper components with > > > > clean and consistent interfaces (i.e. interfaces that can be used by > > > > humans who have to understand them) are not restricted to particular > > > > programming languages and I think in fact are quite fundamental to > > > > software engineering. > > > > > > With ruby you can have clean human visible interfaces which (at run > > > time) instantiate as name-clash free computer usable interfaces for a > > > small, one-time cost (defs become slightly slower). > > > > I'm not sure whether I understand you correctly here - apparently there is > > something missing: I understand that you generate unique symbols. It's > > not completely clear to me how you integrate that. Taking the former > > example of LibA and LibB which both want to add method "foo" to class > > String. How would that be done using this Symbol extension? > > I have a feeling this is a deeper subject than I can adequately > address in an e-mail tonight, but now is when I am, so: > > I have tackled this problem at least three times in ruby. Each > time I came up with a different solution (in fact, it wasn't until I was > well into the third solution that I recognized the pattern). In each > case, there were the following similarities (ignoring the many > differences): > > * I had started with a prototype that extended the core classes > * I wanted to build on the prototype but limit the extensions to > core classes for "local use" > * I wanted the extensions to be freely available to some body of > code > * I wanted hide them from outsiders, either to protect them, to > protect myself, or to prevent name clashes > * I wanted the extensions to appear as regular methods to the code > that was allowed to call them > * I wanted to be able to mostly forget about how it worked once I > had it working > > The three solutions I've tried that come to mind as follows were > roughly: > > 1) Use proxy/wrapper objects > > In this situation I had initially worried about name-space clashes, > so had used the convention that all methods added to class C by libX > would be named "C#libX_foo"; after I had done a bit of this I realized > that I could add method C#libX that returned a proxy/wrapper object that > "knew" all the methods foo_1,foo_2, etc. Then I could write c.libX.foo > instead of c.libX_foo, but get the same results. > After a bit of this I started having the wrapper objects handle all > the regular methods in the same way as it handled the extensions, so > that I wouldn't have to worry about when to add the libX. I wound up > always adding it, and then (as I refactored) "moving it up stream" so > that I never added it (IIRC, there was a brief chaos in my thinking at > this point where I was effectively calling things like > v.libX.libY.libX.foo because I was unclear about what I had or wanted). Do you mean by "moving it upstream" that you did the wrapping at the point where some instance entered the lib? Example for what I mean: public def foo(o) bar( o.libX ) end private def bar(o) # "o" is already libX proxy o.method_that_is_specific_to_libx() o.standard_method_of_o_s_class() end Interesting enough we seem to be able to use "::" instead of "." making thinks like this possible: "foo".libx::bar Where libx will return the proxy which then can handle bar. This looks like we had separated the method namespace of String methods. But I guess you discovered that already. > Once this settled out, I found that almost all of the libX/libY's > had vanished and I was left with code that mostly looked like what I had > started with. > > 2) Use a method dispatcher > > This was a more ambitious attempt to build on the above. I decided > to trap (using reflection) any attempts to add methods to the core > classes (and had some angst over defining what I meant by this). I then > used my symbol-uniqueification thing to rename the functions and built a > table for redirection via method missing. This was in part dictated by > an attempt to work with code that mimicked part of the semantics of > CLOS (the Common Lisp Object System) as well. > The dispatcher would route messages to the proper extension based > on the caller (as well as on other information); libX would see the libX > version, libY would see the libY version, and others would get normal > method_missing behaviour. So your dispatcher has some similarities with my map approach, probably the main difference being the better syntactic integration (i.e. methods directly callable vs. "map[o.class].call()"). > As I went on, the overhead began to seem > excessive/unneeded (although I don't recall doing any actual performance > testing) so I switched to only going the full dispatch route when there > was an actual conflict. > Most of the CLOS style dispatching melted under duck typing > (*smile* my version) as well. So you did a local extension to Duck Typing (TM) as well? *shudder* :-)) > 3) Extend the object as they come in > > The third situation already had a "taint-wall" of sorts, and so I > played with simply extending the individual objects as they came in. > Not a general solution, but it worked out nicely. > > -------------------------------------- > > The conclusion: in at least three cases I started out with a design > that relied on extending core classes in application specific ways. In > each case I managed to keep the semantics but partition them so as to > minimize/eliminate the effects on outside code. None of them was > perfect but they were all relatively easy to implement and none of them > resulted in cluttering up the application code (except as I was working > out what I wanted). In short, all of them were acceptable. Yes, that's true. When I spoke about std class extensions that should be done with care, then I meant exactly this: if it's done directly, then those methods better fit the class's semantics (and ultimately migrate into some kind of std lib - either Ruby std lib or extension.rb), if done via mechanisms like you describe, much more extensions are acceptable. > Thus my belief that it is safe to extend the core classes if that > seems like the right thing to do. In most cases it will turn out that > the extension is general enough to warrant inclusion in the > "extension.rb" trick bag, and most of the rest will never face the test > of libraryification. But if that awful fate befalls them, a little > meta-programming should be able to solve it. And it *should* be done to avoid nasty effects unless we're talking about small scale scripts. > Ultimately (especially if this comes up again, now that I'm on the > watch for it) I would like an uber-solution. But even not knowing what > that would look like, I am comfortable extending classes as needed, > secure in the knowledge that ruby won't fail me in my hour(s) of need. If this kind of situation (several libs extending core classes) was very common. I don't oversee all Ruby libs and thus don't know how common that is. From what I read here, I get the feeling that it's not too common. Hm... Kind regards robert