Hi --
David A. Black wrote:
[...]
I write class methods sometimes (I know those are singleton with an
asterisk next to them, but still),
Sometimes they're the right thing...
and I think that extending core
objects with modules is a frequently overlooked and very powerful
alternative to reopening core classes and adding methods.
This kind of thing:
class String
def method_I_need_once_or_twice
...
is almost always overkill. It's sort of the core-functionality
counterpart of using global variables.
True. (Even though I occasionally do this.)
Extending an object is a much
more precise operation -- and has the additional merit, I find, of
really making you think about whether it's worth bothering to the
extend the object instead of working with what the object can already
do.
Agreed. But why is it worth extending an object instead of subclassing
it?
In other words,
class SpecialString < String
def method_I_need_once_or_twice
...
end
...
@some_string = SpecialString.new(@some_string)
seems to me like the right way to do this -- you can check if a given
String instance is a SpecialString or a plain String, or simply use
overriding and polymorphism for delegation.
Yet, if I understand you correctly, you and James are claiming that it
is preferable to do
class << @some_string
def method_I_need_once_or_twice
end
Do I understand correctly? If so, why? I hesitate to contradict such
experts as you and James, but I'm really not seeing the benefit. For
one thing, type-checking completely falls down with this pattern.
Polymorphism may or may not, depending on how it's implemented. And I
don't see a single advantage that we get in return for the loss of
type-checking. What am I missing?
I'm not saying (and I don't think James would say) that this is a
"winner-take-all" situation where we all have to choose one technique
and do only that. But I think extending core objects is a very good
way to add functionality to them.
It sounds like by type-checking you mean class-checking (or
ancestry-checking). It's kind of a circular argument, in the sense
that if you decide that it's important to know an object's class
because you're relying on that to tell you exactly what the object
does, then it's bad if the object does other stuff (i.e., if its type
diverges from its class).
It's certainly possible to look at things that way, but it seems to me
that it means you're fighting Ruby. The entire thrust of Ruby's object
model is to put the focus on the objects rather than their classes.
(Yes, I know that classes are objects
Them too -- which is why
class methods are manifested as singleton methods on class objects,
rather than some completely separate language-level construct.)
Here's one of my favorite historical observations about Ruby, where
ruby10 is the Ruby 1.0 interpreter:
[dblack@ruby-versions ~]$ ruby10 -e 'puts "hi"'-e:1: NameError:
undefined method `puts' for main(Object)
[dblack@ruby-versions ~]$ ruby10 -e 'a = "hi"; class << a; def talk;
print self + "!\n"; end; end; a.talk'
hi!
In other words, Ruby 1.0 had no puts statement... but it *did* have
singleton classes. That's a great corrective to the perception I think
some people have that singleton behaviors are some kind of
meta-wizard-add-on complication to the language. They're not; the
language was designed from the beginning to make it possible to
engineer objects on an individual basis.
I've heard people say that, while that's true, it's undisciplined to
actually use any of these techniques. I disagree radically. Of course,
I don't spend all that much of my time as a Ruby programmer writing
singleton methods -- but I do regard the individual object as the
center of gravity, and I regard classes largely as a convenience-macro
for creating objects bundled with certain behaviors at their birth
that may or may not represent what they do during their lifetimes.
This is also why, in my book and in my training, I present singleton
methods *first*, and classes second. (And I completely understand why
Matt Neuberg introduces modules before classes[1] -- which I usually
don't but it's an extremely intriguing idea.)
Classes exist in Ruby, and no one is awarding points based on how many
singleton methods one writes. But for me, anyway, the class part of
the model floats on top of the real business, which is the objects. So
I don't like to commit myself to having things break just because
objects aren't aligned perfectly type-to-class.
(There's more to say but that's long enough for now
David
[1]
http://www.apeth.com/rbappscript/02justenoughruby.html