On Fri, 2004-10-15 at 03:24, Robert Klemme wrote:
> "Markus" <markus / reality.com> schrieb im Newsbeitrag
> > > Hm...  I prefer a simpler solution.  After all, what would you do 
> > > if you had libA and libB which both add method String#foo and
> > > which both are able to detect the name clash and report it? 
> > > You'll most likely get an exception and can't use libA and libB
> > > together.  Of course, those libs could be written to automatically
> > > choose appropriate names, possibly even with dynamic generated
> > > prefix.  But this likely becomes unmanageable fast.
> >
> >      My solution (*smile* involving extending Symbol) doesn't get
> > unmanageable at all, so far as I've seen, uses:
> >
> > class Symbol
> >     @@unique_sym_counter = 0
> >     def Symbol.unique(prefix="gloop")
> >         prefix = prefix.to_s.
> >               gsub(/\+/,'_plus_').
> >               gsub(/\*/,'_times_').
> >               gsub(/-/,'_minus_').
> >               gsub(/\//,'_div_').
> >               gsub(/\!/,'_bang_')
> >         ("#{prefix.to_s}__#{@@unique_sym_counter += 1}").intern
> >         end
> >     end
> >
> > which is in part an example of:
> >
> > > >      All of these precautions can be neatly encapsulated themselves,
> > > > so you don't even need to mess with them while you're working on your
> > > > code.

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

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

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.

     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.

     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.

    -- Markus

P.S. I may have time for one or two quick rounds tomorrow, but after
that I'll be off-list for a week.