On Friday, May 20, 2011 12:58:26 PM Michael S. wrote:
My question is, what kind of mental model do you use when you program in
Ruby?
I’m not sure of the best way to describe this, and I suspect others have
already done a decent job, but in any language, I have two main things
going
on in my head. First is the semantics of the language in question.
Second is
just enough of the implementation, usually more closely related to the
semantics than to the actual C code, that I have an intuition of what
kind of
things are likely to be more or less efficient.
How do you think about objects? Do you see them as elements carrying
with them their own methods, bundled with their data?
Not quite, but close. With respect to the object or class I’m currently
developing, I think of it almost as an independent program capable of
sending
and receiving messages, much as I think of Erlang processes. When using
objects, I think of them as nouns, and the methods as verbs.
How about the flow of the program: Whenever there’s a method call, do you
picture the method to be inside the receiver - just like it would be in a
real-life object -, or since you know that in the underlying implementation
the method is stored in the class, you just think about a procedure call
with a self variable being the receiver?
I think of it as being “inside the receiver” in the sense that your
personality, your decisions, your actions and reactions, are all inside
your
brain. That some might be genetic (and there is of course dispute about
this)
is an irrelevant detail.
There are practical reasons for this, also: How an object responds to a
method
call really is up to the object. Aside from method_missing and other fun
tricks, objects also have metaclasses, which means you can define a
method on
a single object.
Do you think using the OOP abstraction without knowing the internals can be
harmful?
I agree with Robert; worrying about internals when you don’t have to is
harmful. While I do have a thread running in my head thinking about
performance considerations, even that is irrelevant for most programs
most of
the time.
So, for your case:
My case for that (even if I tend not to believe so) would be that
someone might be tempted to think that during an object instanciation, all
the instance variables AND methods gets duplicated for this particular
instance, which isn’t the case - yet, that’s what the abstraction pushes us
to believe.
If you don’t know JavaScript well, I would strongly suggest picking it
up.
Read pretty much anything Douglas Crockford has to say about it, and
play with
different OO patterns. I’m going to use this as an analogy here, so I’ll
try
to include enough background that it’s understandable if you don’t know
any
JavaScript, or if you still think JavaScript is “Java Lite”.
JavaScript objects behave like hashes, and the two can be used almost
interchangeably. Methods are just functions (generally anonymous) which
have
been stored in the hash, and a syntax for calling them which sets ‘this’
but you can apply almost any function to almost any object. Many
implementations allow you to get a method’s source easily – playing
around
with this, it seems that when you attempt to coerce a method into a
string,
you get the source back.
Your choices for inheritance are either to use JavaScript’s own
prototypal
inheritance, or to write your own inheritance – and your only choice
for
multiple inheritance is to roll your own. With prototypal inheritance,
any
time you try to access a property (either an instance variable or a
method) of
a given object, it checks that object first, then its prototype object,
then
the prototype’s prototype, and so on, arbitrarily deep.
Rolling your own is much more flexible – you just create a new, empty
object
(as easy as {}) and start adding methods to it. Basic inheritance would
just
mean calling some “constructor” method which returns an object, then
modifying
it in the child “constructor” method.
Now, like with your example, as a newbie, you might be tempted to think
that:
- Functions are stored as strings.
- Internal, inline functions are therefore inefficient, because you’re
creating a new string each time.
- Prototypal inheritance is slow, especially at any depth, since you’ll
have
to trace the entire ancestry for each access.
- Roll-your-own inheritance is tremendously inefficient, since even if
the
initial object creation (prototypal or otherwise) was efficient, you’re
taking
the child and modifying it, thus leading to worse performance.
One of these is still true, but the others are false. Most surprisingly,
rolling your own inheritance may lead to a slower constructor – maybe
but in the v8 engine (used in Chrome), there’s no performance penalty
whatsoever once the objects are created. Despite the highly dynamic
nature of
what I’m calling roll-your-own inheritance, which feels like it should
be less
efficient than calling Object.extend in Ruby on every single object, the
resultant objects behave very similarly to objects created in
statically-typed
languages – that is, they’re fast!
I just wrote several paragraphs setting up the problem and explaining
why it’s
not a problem. That is why I think while internals are a great learning
experience, a best practice is to ignore implementation details,
particularly
performance implications, until you actually care.
That, and think bigger. Suppose it was true that all the instance
variables
and methods got duplicated for a given instance. So what? It’s still
O(1) with
regard to the algorithm I actually care about, unless that algorithm
consists
of adding methods to the parent class based on input and then creating a
bunch
of objects.