The problem is that you are splicing code maintained by one person with
code maintained by another. Who is responsible for this? You claim
"buyer beware", that the user of #class= is responsible, but earlier in
this thread you state [ruby-talk 104578]:
But, I think simply placing checks in certain appropriate places would
alleviate the problem. Sometime today I think I'll try putting type
checks
in the R_CAST macro and see how that works.
If you really believed that this method was "buyer beware", why are you
suggesting the Ruby internals be fixed? That implies that Ruby is
responsible for failures beyond a simple exception, that Ruby should be
altered to make it safe for #class= .
There are two problems with #class= that I was talking about, not just one.
The first, and the one that causes the most turbulent problems, is that a lot
of C code internally considers self VALUEs to be of various static types, and
there are few checks to ensure proper type or status before self VALUEs are
used. That causes segmentation faults and potentially other various
nastiness. This problem can be solved with protective code, but only someone
with a lot of knowledge of Ruby's internals would know where every dangerous
code point is; that's not something I could not do by myself.
The other problem is when one class simply doesn't know how to handle the data
contained in an object which was created by another class. That's the "buyer
beware" issue I was talking about. Assuming #class= were to be implemented
the way we've been discussing it, what methods do when you assign a new class
to an existing object is something the developer decides; they decide which
class they assign to which object, and what the end result will be.
But there is a third case:
* the first use of a method on the new object quietly fails, possibly
in ways that cause permanent damage to your system.
This is already possible anyway. Ruby has no type checking, and it's very
possible to make method calls on objects that you expect to do one thing, but
which do another. From what I understand, this hasn't been much of a
problem; I think people consider this one possible result of duck-typing, and
it just hasn't manifested itself as destructively as some have feared (myself
included).
The immediate problem is the crashing in C code. Beyond that, its general
break from OO causes OO dysfunction which, like duck typing, can cause
unexpected behaviors. Its not so much the unexpected behavior that's the
problem (you can get that easily in lots of ways already), its that it simply
isn't OO.
One can protect against this sort of problem safe is to turn #class=
off by default, adding a hook on Object:
def update_instance_for_changed_class( old_instance )
raise 'Unimplemented'
end
I also considered something like this as just one of a lot of things that
could be done to make #class= safe, but all this work starts to make #class=
look like just a very bothersome, non-OO version of #become. I'm really more
a fan of #become over #class= now.
Note that to make the api safe, we had to turn it off for all but
explicitly coded conversions. If we have to be so explicit to be safe,
why not just have an explicit method to do the conversion? Note matz
has already made this design decision. When converting types one uses
explicit, clearly implemented methods like #to_s or #to_a to convert
one type to another.
Well, this is certainly the easiest route; just avoid the whole thing
completely and let to_ be the conversion mechanism.
Finally, I have not yet seen an example that could not be handled
trivially in some other manner, using delegators, or re ordering code a
bit:
class WriteLogger < SimpleDelegator
def write( args* )
$stderr.puts "write called"
super()
end
end
socket = WriteLogger.new( socket )
socket.write( ... )
The scope of this is limited though. If you need the logger to replace, say,
a socket object that was created by a library, the logger would only function
in the scope of your method(s); once the logger went out of scope, the rest
of the library would go back to using the socket object it created.
It is irresponsible to add a method to Object:
* that is typically broken by default
* that introduce risks that have to be bandaged by more cruft
* that obfuscates the problem it attempts to solve
* that attempts to solve problems which are better solved in other
manners
#class= is pretty much useless, unless you want to break something.
And for breaking things, I generally find that #raise fits my needs.
Ruby already lets you break things wonderfully in a colorful variety of ways.
#class= does have issues, but the problems that arise from inserting methods
into an object which don't understand the object very well is really
something you can do easily in a lot of other ways. #class=' biggest trouble
is that it causes crashed with internal C code, and that it breaks from OO
tradition.
Sean O'Dell