4 ms·
I always thought that the "message passing" style of OO (which I always hated as a term, because it implies asynchronicity, but I digress) is firmly something e
by codeflo 3y ago
I always thought that the "message passing" style of OO (which I always hated as a term, because it implies asynchronicity, but I digress) is firmly something else than the "classes and interfaces" style of OO, and that it's an unfortunate accident of history that we give them the same name.
- vidarh 3y agoI think part of the problem of trying to separate the two is that the most prominent implementations appear deceptively similar on the surface, even with unfamiliar syntax. E.g. put even Smalltalk in front of someone familiar with C++, and they'll quickly latch on to the similarities once a few of the basics are explained, to the point that explaining message passing as different from method invocation is tricky, not least because so much of message dispatch is implemented in terms of method invocation. This is ironically exacerbated when what is often presented as the important bit of Smalltalk are examples like the one in the article of allowing definition of control structures (you can do that in Ruby too, e.g. a partial impl: "def true.ifTrue = yield" - now you can do "(1 < 5).ifTrue { ... called if true }" - with a bit more thought you can chain ifTrue/ifFalse). But while that's neat, that doesn't even require dynamic dispatch, just the combination of being able to invoke methods/dispatch messages to true and false, combined with convenient syntax and support for closures. You could add that to a language and still not have a Smalltalk descendant in any meaningful way. What matters much more is that objects at least may take control over the target of a message dispatch even if they often don't, whereas method invocations in "those other" OO languages is mainly controlled by the class definition, and that is often glossed over as an advanced subject or "scary magic". For example how Ruby ORMs tend to use introspection to let you dynamically treat columns on database tables as methods based on the actual current schema of the database - in other words the ability to do not just dynamic dispatch but late binding.
- dfox 3y agoST80 does not really do the object controlled message dispatch that was there in earlier Smalltalks. Well, you can override Behavior>>#doesNotUnderstand: and do all sorts of cool tricks with that, but it is mostly meant as error-recovery path (which is partly apparent from the name), not as something that should be regularly used. One weird aspect of Smalltalk that more or less directly comes from the control structures implemented as messages taking blocks is that on the language level there are two distinct function-like objects: methods and blocks (ie. lambdas) that behave differently and interact with each other (return statement is scoped to method and only valid during the dynamic extent of said method invocation).
- vidarh 3y ago> ST80 does not really do the object controlled message dispatch that was there in earlier Smalltalks. Well, you can override Behavior>>#doesNotUnderstand: and do all sorts of cool tricks with that, but it is mostly meant as error-recovery path (which is partly apparent from the name), not as something that should be regularly used. The same way you can override "method_missing" in Ruby but you apply it as rarely as possible, but the dispatch is still dynamic and methods can be overridden and dynamically defined. That's the point. Not that you literally implement a dispatch directly on the object. Put another way, the main distinction is that the precise method body invoked by sending a given message to an object may be impossible to statically determine. > One weird aspect of Smalltalk that more or less directly comes from the control structures implemented as messages taking blocks is that on the language level there are two distinct function-like objects: methods and blocks (ie. lambdas) that behave differently and interact with each other (return statement is scoped to method and only valid during the dynamic extent of said method invocation). Ruby sort-of inherits this too, but at any point where you take the value of a block, it becomes an object - it's purely an implementation artefact, and with lambda/proc providing both lexical and method-local scope for return.
- em-bee 3y agothis distinction is something i have been struggling with for some time. i am coming from pike which only uses method invocation terminology (in pike it's actually called "function call"), and never even hints at message passing, yet in pike objects can take control over the target of a function call and even dynamically create functions based on arguments given. when learning smalltalk i could not see the what was so special about message passing, and why it is even called that. consequently, i find the distinction between message passing vs function or method calling academic. more interesting are distinctions like static vs dynamic dispatch and early vs late binding and whether things are defined at compile time or runtime. the term "message passing" always made me feel like this should be something completely different and not even remotely similar to function calling. yet i couldn't see that difference and that left me irritated because i felt like i was missing something.
- vidarh 3y agoThe language is made a lot more confusing by the fact that most of the time you can improve performance of a conceptually message-passing implementation by implementing it as much as possible in terms of invocation, so it's totally reasonably why even very dynamic implementations would end up spoken of as method invocation. E.g. my long-languishing partial Ruby compiler uses C++ style vtables because they're fast and the "only" challenge is that you need to propagate method re-definitions down a chain of descendant classes (and avoid overwriting overridden versions in the descendants). In practical terms, pseudo-code for a method dispatch is ob->class_ptr.vtable[method_slot](args...) and all of the dynamism happens with a combination of dynamically overwriting and propagating method pointers down the vtable chain (I was worried it'd lead to way too much memory spent on sparse vtables and having to fall back on a hash table for less-used method names, but in practice the number of classes is usually very constrained) and filling in thunks that forwards to method_missing for names that are seen in the system but not implemented by the current class. Effectively you can consider the vtables as perfect pre-filled caches. To someone casually looking at them to get an idea of Ruby, it'll look like Ruby's object model is almost the same as C++'s. So I agree with you that the key practical difference is static vs. dynamic dispatch and early vs. late binding. With those distinctions you don't really even need to make the compile vs. runtime distinction. That is, you can statically compile something that includes dynamic calls to modify the object model, like my prototype compiler. Elsewhere I suggested one way of looking at it is that to distinguish the C++/Java etc. and Smalltalk model of OO, the key test is how common it is for there to be code where determining which method body will be invoked by a given call devolves to the halting problem if you don't have the precise inputs ahead of time (e.g. in Ruby, almost every ORM would cause this).