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.