Messages versus slots - two OOP paradigms
I've had this discussion with Oleg before, but this still keeps coming up again and again. See, alot of people are pissed at Ruby that you cannot do this
something = lambda do # execute things end something() # won't work
and also cannot do this
meth = some_object.method(:length) meth(with_argument) # call the meth
However this comes from the fact that Ruby is a message-passing language as opposed to a slot language. What I tend to call a slot language is something that assumes objects are nothing more than glorified hash tables. The keys then become either instance variables or, if they store a function, "callables" (the way Python calls it).
So you might have an object Car that has:
[ Car ] -> [weight, price, drive()]
all on the same level of the namespace. Languages that operate on the "slots" paradigm usually have the following properties:
- It is very easy to rebind a method to another object - just copy the value of the slot
- You can iterate over both ivars and methods in the same flow
- Encapsulation is a decoration since everything in fact still is in the same table
- You can call variables directly since local namespace is also a glorified hashmap with keys for variable names and concent that iscallable.
Due to various reasons I dislike this approach. First of all, I tend to look at objects as actors that receive messages. That is, the number of internal variables stored within an object should not be visible from the outside, at least not in a formal way. Imagine a way to figure out the length of a string in a slot language.
str.len # is it a magic variable? str.len() # is it a method? # or do we need a shitty workaround which wil call some kind of __length__? len(str) # WTF does that do??
There is ambiguity whether the value is callable or not, and this ambiguity cannot be resolved automatically because this expression is not specific in a slot language:
# will it be a function ref? # or will it be the length itself? m = str.len
and therefore languages like Python will require explicit calling all the time. This might be entering parens, or doing
// boom! we transplanted a method someVar.smartMethod = another.otherMethod;
All of the rest are actually inelegant kludges. First of all, since a method can be used like a value for moving code from place to place, you always need explicit calling. Second, your slots in the object do not distinguish between values and methods so you have to additionally question every value in the slot table whether it is a method or a variable. Also, in slot languages you need to specify that an instance variable is private or not (since normally everything is in one big hashtable anyway).
On the other side we have message-passing languages, like Smalltalk and Ruby. In there anything you retrieve from an object passes through a method call whether you want it or not, because there are two namespaces - one for ivars and the other for methods.
You know that ivars from the outside of the objects are off-limits, and you know that everything exposed to the outside world iscallable, by definition. You also get the following benefits:
- everything only ever goes through getters and setters, so you just don't have to think about them all the time
- you tend to avoid using objects as glorified hashmaps
- you get alot of smart syntax shortcuts
- refactoring a property into a getter/setter pair is a non-issue to the consumer code
Message-passing languages also adhere to the UAP.
When implementing your next programming language, first make it a message-passing system, and if you need performance improvements make internal opaque shortcuts to bypass the getter/setter infrastructure when direct properties are accessed. You will spare alot of people alot of useless guessing and typing.comments powered by Disqus