Agreed. While I think matz is a great language designer, I loved Alan Kay's philosophy. I'd like some language that is OOP centric in nature, fast, has an elegant syntax and learns from erlang's model (elixir isn't it unfortunately, but some ideas it got right).
bglusman 12 hours ago [-]
If you want OOP than, yes, Elixir isn't it... maybe Pony? Curious what else you don't like about Elixir though besides not being OOP... it's definitely got messaging!
zukzuk 11 hours ago [-]
A robust static type system is what was missing for me, at least last time I looked.
munificent 10 hours ago [-]
I like this article a lot but if you want to dig deeper and understand some of the negative consequences of Ruby's approach, you might like these two articles I wrote:
Love Ruby. Wish I could use it more often. Always end up reaching for Python in day-to-day because of how mature and well-docunented the libs are.
Anyone who likes this kind of stuff, highly recommend Metaprogramming Ruby [2] by Paolo Perrota. Great look into Ruby innards and inspiring code examples. Gets me pumped
bjoli 10 hours ago [-]
Leaving the comfort of lisp, I have started to wonder: why are all the looping facilities in other languages so awful?
It handles 98% of all loops I write, meaning I don't have to manage state (even for things like treating things like circular data). I find it removes many of the stupid errors I make when iterating over data, especially when I need nested loops and conditional accumulation.
I find it so provoking that someone implemented foreach and went "yes. That should be enough for everyone!"
tangus 9 hours ago [-]
Looping in Ruby is the opposite of awful. You only have to define an `#each` method in your class and include the `Enumerable` module, and you get a plethora of composable methods to iterate and accumulate in every which way. I find it nothing short of brilliant.
bjoli 7 hours ago [-]
Yet, iterating over several collections and accumulating values is awkward.
It is not that it is easy to implement the iteration protocol, it is that the construct for looping universally sucks. Even the awful loop macro from common lisp runs laps around most other facilities. Racket's is even less powerful, but is miles better than what python offers.
Ruby can do some destructuring, but not enough to actually be useful.
tangus 6 hours ago [-]
It may be somewhat awkward; zipping collections to iterate over them in unison makes the first one (the sender) seem special, while it not necessarily is. And accumulating more than one value isn't always straightforward; you usually have to reduce explicitly. But it's so much better in the general case!
Like the example you showcase your macro with. In Ruby it would be:
You can see what it does at a glance, you don't have to immerse yourself inside the directives to follow the logic. And when you can't do that, declaring iterators and accumulators outside a loop and then iterating and accumulating "by hand" yields code not so different than your more complex examples.
def partition (arr)
yes = []
no = []
arr.each do |elt|
if yield elt
yes << elt
else
no << elt
end
end
[yes, no]
end
nurettin 7 hours ago [-]
How is that different from select map and reduce in ruby?
Even something as basic as "if" is done with message passing and blocks in Smalltalk.
There's a method named "ifTrue:ifFalse:" in Smalltalk (with each ":" expecting an argument, in this case, a block).
You can also chain messages and make phrases: `anObject aMethod; anotherMethod; yourself.`
The Ruby equivalent has repetition: `an_object.a_method; an_object.another_method; an_object`
or requires a block: `an_object.tap { _1.a_method; _1.another_method}` (and we usually use newlines instead of ";")
dragonwriter 10 hours ago [-]
In Ruby, methods called mainly for side effects rather than return value conventionally return the object itself, and so can be chanined by:
an_object.a_method.another_method
but, sure, it's less elegant (or at least less terse) than Smalltalk for the specific case of chaining invocations of methods with meaningful return values which are called for their side effects while discarding the return values.
wry_discontent 13 hours ago [-]
I have found that chaining things in Smalltalk get immensely painful after just a couple elements. Unlike most other languages, I find myself reaching for silly little var names for lots of different things I would normally just let flow.
isr 6 hours ago [-]
Really? To each their own, but honestly, I found smalltalks way of chaining things to be one of the most elegant parts of what is admittedly a syntactically-simple language (the old "fits on a postcard" thing).
With Smalltalk, with regard to return values or chaining, you get to have your cake and eat it too.
Your methods CAN return sensible values which don't necessarily have to be the original object. BUT, if you just want to chain a bunch of sends TO A PARTICULAR OBJECT, you can use ;, and chain the sends together without requiring that each intermediate method returns the original object.
That combined with the fact that chaining sends requires no special syntax. You just write them out as a single sentence (that's what it feels like), and you can format it across multiple lines however you wish. There's no syntax requirements getting in the way.
Just finish the whole thing with a dot. Again, just like a regular sentence.
And if you find precedence or readibility becoming confusing, then just put stuff in parens to make certain portions clearer. There's absolutely no harm in doing do, even if in your particular use case the normal precedence rules would have sufficed anyway.
Smalltalk
complex method chaining;
again (as mentioned previously)
reads like English.
pansa2 15 hours ago [-]
> But Python also looks up methods at runtime. Does that mean Python also does message passing? Not quite.
I don't think Ruby's "message passing" is fundamentally different from Python's "method calls". Ultimately, both languages implementations are very similar: both look up methods by name in a hash table and then call them.
IMO "message passing" is just an alternative metaphor for what Ruby does when you type `object.name`. The metaphor fits Ruby but not Python, because the languages do three things differently:
- Ruby only looks for `name` in `object.class` (and its superclasses), whereas Python first looks in `object` itself
- If Ruby finds `name`, it's guaranteed to be a method whereas in Python it could be a different kind of value
- Ruby immediately calls the method once it's found, whereas Python (generally) doesn't - instead it returns a binding to be called later
This means that in Ruby, `object.name` is always calling a method defined on `object.class`, with `self` set to `object`. That can be re-interpreted as "sending a message" to `object.class`.
In Python, `object.name` is a more general value lookup - maybe the result will be callable, maybe not.
dragonwriter 9 hours ago [-]
> If Ruby finds `name`, it's guaranteed to be a method whereas in Python it could be a different kind of value
This is the big difference, Ruby objects have no members other than methods, external entities cannot read data from the object only call methods; the object controls how it responds.
There's ways that seem to bypass this in Ruby, but they are cooperative, relying on calling other methods on the object, like accessing instance variables by calling object.instance_variable_get(:@foo).
lloeki 14 hours ago [-]
> both look up methods by name in a hash table and then call them.
Except Ruby doesn't? cue `method_missing`. If you take only trivial examples you're not going to see much difference, this starts to show when you involve more advanced situations e.g with inheritance, and then you're drilling into singleton classes.
> Ruby immediately calls the method once it's found, whereas Python (generally) doesn't - instead it returns a binding to be called later
Again incorrect, `foo.bar` in Ruby and Python are two very fundamentally different things.
Python returns a binding to a method because it's an attribute accessor; when you throw inheritance into the mix it ends up that that attribute is inherited from the parent class, bound to the instance (which really in python means pass `self` as first argument), and - schematically - adding `()` after that ends up calling that bound method. If there's no attribute that's a no method error. It's all very C-ish and make believe, barely a notch above Go structs and their functions. The closest parallel in Ruby would be `foo.method(:bar).call()`
By contrast Ruby is going to send the :bar message along the inheritance chain, and if someone can respond it's going to invoke the responder's code, and surprise surprise method_missing happens only if it has exhausted inheritance but it's itself a method-slash-message; Oh and by the way the message passing is naturally so lazy that you can actually modify the inheritance chain -in flight- and inject a parent responder right before calling `super`. The whole notion of `binding` is a very concrete construct, way more rich that simply "hey I'm passing self as first argument". It becomes even more strange to "C&al. folks" when you start to involve singleton classes and start to realise weird things like Ruby classes are merely instances of the class Class and it's all instance turtles all the way down and all stupidly simple but you gotta have to wrap your head around it.
I surmise that so many differences and surprises have with Ruby are because most languages have some ALGOL legacy and Ruby is a conceptual heir to Smalltalk (and LISP†); the whole concept of open classes being another one: nothing is ever "finished" in Ruby!
Most of the time you don't have to care about these differences, until you do.
† While code isn't quite S-expr data in Ruby, there are more than enough first-class facilities that you can create and inject code entirely dynamically without resorting to `eval`ing strings.
zrail 10 hours ago [-]
Nice summary. I've been using Ruby both professionally and not for going on 20 years and I just today learned about singleton_class when attempting to build something like Rails' view helpers from first principles.
lloeki 10 hours ago [-]
Thanks. I've been trying to put these bits in a succinct format:
Each chapter kind of builds up on the previous one. It's still WIP and far from complete but what's there has helped me onboard a few people to Ruby already.
You might be interested in the Classes and Ancestry chapters.
jonhohle 9 hours ago [-]
> This means that in Ruby, `object.name` is always calling a method defined on `object.class`, with `self` set to `object`. That can be re-interpreted as "sending a message" to `object.class`.
Ruby Instances can have their own methods and override methods separate from their class.
dragonwriter 3 hours ago [-]
More precisely, Ruby instances each have a (empty initially) “singleton class” that comes before their “regular” class in method resolution order, and to which methods can be added.
shevy-java 15 hours ago [-]
But here you refer to implementation details. Isn't the point about message passing that objects themselves communicate with one another through such messages? And in turn, messages can also be assumed to be small objects. I don't think any of those implementations really fulfil that as a meaning. Or perhaps I misunderstood Alan Kay here. He drew inspiration more from biological cells and communication pathways therein. Naturally biology can not be mapped 1:1 onto in-silico hardware, that wouldn't even make sense either - but for instance, erlang's model appears to me closer to different objects happily communicating with one another in a very flexible, safe, fault-tolerant manner. I don't think it is the implementation detail that is about message passing. I also don't think "sending a message" is the confinement either - it's an important but, but does not appear to capture all that is meant with a "message". A message could be many things, including objects that themselves could change at any moment in time again. I see it more as an intrinsic part of communication at all times.
skywhopper 15 hours ago [-]
I loved Smalltalk when I had to learn it for my first job out of college. Clean and clear OO with a flexible, play-inside-of-your-code runtime. Unfortunately the Smalltalk community is pretty small.
So imagine my delight when I found Ruby in 2005. It took the best of Perl and the best of Smalltalk and gave it a much better syntax than either, plus it had a massively growing community.
Ruby breaks a lot of the rules for what people claim they want (or should be allowed) from a programming language these days, but for me there’s still no more joyful and easy programming language to express my ideas.
pjmlp 13 hours ago [-]
Not all the best parts of Smalltalk, otherwise a IDE based experience with a JIT compiler would be there from the early days, that is an integral part of Smalltalk experience as developer.
lazyvar 14 hours ago [-]
I think this misses the point. `times` is "better" than `for` because it's declarative, reads like English, etc. Which of course are opinions, but the implementation details (messaging passing or not) are irrelevant.
Example: Swift and Kotlin can do `Int#times` and don't need message passing to get it done.
jonhohle 9 hours ago [-]
Dynamic types in Swift do use message passing. Int/NSInteger is a struct/value type, but NSNumber is a dynamic type that would receive a message.
zahlman 11 hours ago [-]
A while back I tried out SuperCollider for programmatic music generation... I distinctly remember thinking, "this language feels like some weird awkward blend of Smalltalk and Ruby".
Somehow I hadn't thought of the two as similar or related to each other.
Anyway, certainly you can write in this style in Python, since functions are first-class objects. It just has limits on its anonymous functions, and doesn't happen to provide a `times` method on integers (at least partly because the old parser would have struggled with seeing `10.times` and thinking "this should be a floating-point number, but `t` isn't a decimal digit").
>>> class rint(int): # r for Ruby, of course
... def times(self, func):
... for i in range(self):
... func(i)
...
>>> rint(3).times(print)
0
1
2
> In Python, Java or C++, calling object.method() asks the compiler to find the method in the class and call it.
This is incorrect, as noted later. In Python, the lookup occurs at runtime. It also checks the object first, in most cases.
... The writing keeps anticipating my objections and then sort of correcting them and leaving something else not quite right. So trying to edit as I read has been frustrating. The point being, I just don't buy the philosophical difference that the author is trying to draw.
In Python the reason you're dealing with "attributes" rather than "messages" is because functions are first-class objects, which entails that they'll be treated the same way as ordinary data. A method call is two separate steps — the method-lookup and the actual attempt to "call" the result — which also has the implication that you can cache a "bound method" for later use. (By comparison, Ruby lets you "cache" the symbol, which in Python-think looks like some kind of magic string that tries to look itself up as an attribute name on other objects.)
But, I contend, this doesn't meaningfully change how the language is used overall.
> By including Enumerable and by implementing each, we get access to so many methods that we didn’t need to manually implement.
Okay, but in Python `map` and `filter` are just builtin functions, and you can use list comprehensions and `functools.reduce`. It's just a difference between external and internal iteration.
bitwize 9 hours ago [-]
Implementing objects as closures interpreting messages passed as parameters is exactly how many Scheme programmers (myself included) wrote "babby's first object system" in Scheme. Such an object system is also presented in SICP.
Amazing. I've heard both sayings before, but not the koan. Yet I somehow meditated upon it without knowing!
stonecharioteer 13 hours ago [-]
Damn, I'm a fulltime Ruby blogger now?
Joker_vD 15 hours ago [-]
> Leaving the handling of the loop to the method allows us to add behaviour to loops that are controlled by the object and not by the user. This is a nice way to add side-effects.
No, it's an absolutely horrible way to "add side-effects" which, usually, is already a horrible idea in its own right.
> Asking an object to iterate over itself allows objects to develop interfaces that dictate how to iterate.
That's true in pretty much any language? And since you need to know which iteration interface you need to use, it's not that much of an advantage.
> And now, when I see: `10.times { |i| puts "i = #{i}" }` I do not see a loop anymore.
Yeah, because it's not a loop: it may or may not run that block 10 times. Seriously, when a programmer's intention is "run something 10 times", the resulting expression arguably should not be "send that something to someone who, hopefully, will execute it 10 times".
isr 6 hours ago [-]
Strongly disagree. THE ENTIRE ETHOS of Smalltalk & Ruby is to leave things up to the object you're communicating with, rather than the call/send site.
Sure, you may not like that mindset, in which case, smalltalk/ruby are ABSOLUTELY not for you. You want something else.
Which is totally fine. Part of the reason behind the Cambrian-explosion of higher level programming paradigms since the 1960's is precisely because there are multiple ways to skin a cat, and different ways resonate with different folks.
Rendered at 03:52:54 GMT+0000 (Coordinated Universal Time) with Vercel.
[0] https://lists.squeakfoundation.org/pipermail/squeak-dev/1998...
https://journal.stuffwithstuff.com/2013/01/13/iteration-insi...
https://journal.stuffwithstuff.com/2013/02/24/iteration-insi...
Anyone who likes this kind of stuff, highly recommend Metaprogramming Ruby [2] by Paolo Perrota. Great look into Ruby innards and inspiring code examples. Gets me pumped
Standing on the shoulders of giants, I made this little abomination: https://rikspucko.koketteriet.se/bjoli/goof-loop
It handles 98% of all loops I write, meaning I don't have to manage state (even for things like treating things like circular data). I find it removes many of the stupid errors I make when iterating over data, especially when I need nested loops and conditional accumulation.
I find it so provoking that someone implemented foreach and went "yes. That should be enough for everyone!"
It is not that it is easy to implement the iteration protocol, it is that the construct for looping universally sucks. Even the awful loop macro from common lisp runs laps around most other facilities. Racket's is even less powerful, but is miles better than what python offers.
Ruby can do some destructuring, but not enough to actually be useful.
Like the example you showcase your macro with. In Ruby it would be:
You can see what it does at a glance, you don't have to immerse yourself inside the directives to follow the logic. And when you can't do that, declaring iterators and accumulators outside a loop and then iterating and accumulating "by hand" yields code not so different than your more complex examples.https://news.ycombinator.com/item?id=45644349
https://tech.stonecharioteer.com/posts/2025/ruby-blocks/
https://news.ycombinator.com/item?id=45681023
https://pharo.org/
There's a method named "ifTrue:ifFalse:" in Smalltalk (with each ":" expecting an argument, in this case, a block).
You can also chain messages and make phrases: `anObject aMethod; anotherMethod; yourself.`
The Ruby equivalent has repetition: `an_object.a_method; an_object.another_method; an_object`
or requires a block: `an_object.tap { _1.a_method; _1.another_method}` (and we usually use newlines instead of ";")
With Smalltalk, with regard to return values or chaining, you get to have your cake and eat it too.
Your methods CAN return sensible values which don't necessarily have to be the original object. BUT, if you just want to chain a bunch of sends TO A PARTICULAR OBJECT, you can use ;, and chain the sends together without requiring that each intermediate method returns the original object.
That combined with the fact that chaining sends requires no special syntax. You just write them out as a single sentence (that's what it feels like), and you can format it across multiple lines however you wish. There's no syntax requirements getting in the way.
Just finish the whole thing with a dot. Again, just like a regular sentence.
And if you find precedence or readibility becoming confusing, then just put stuff in parens to make certain portions clearer. There's absolutely no harm in doing do, even if in your particular use case the normal precedence rules would have sufficed anyway.
I don't think Ruby's "message passing" is fundamentally different from Python's "method calls". Ultimately, both languages implementations are very similar: both look up methods by name in a hash table and then call them.
IMO "message passing" is just an alternative metaphor for what Ruby does when you type `object.name`. The metaphor fits Ruby but not Python, because the languages do three things differently:
- Ruby only looks for `name` in `object.class` (and its superclasses), whereas Python first looks in `object` itself
- If Ruby finds `name`, it's guaranteed to be a method whereas in Python it could be a different kind of value
- Ruby immediately calls the method once it's found, whereas Python (generally) doesn't - instead it returns a binding to be called later
This means that in Ruby, `object.name` is always calling a method defined on `object.class`, with `self` set to `object`. That can be re-interpreted as "sending a message" to `object.class`.
In Python, `object.name` is a more general value lookup - maybe the result will be callable, maybe not.
This is the big difference, Ruby objects have no members other than methods, external entities cannot read data from the object only call methods; the object controls how it responds.
There's ways that seem to bypass this in Ruby, but they are cooperative, relying on calling other methods on the object, like accessing instance variables by calling object.instance_variable_get(:@foo).
Except Ruby doesn't? cue `method_missing`. If you take only trivial examples you're not going to see much difference, this starts to show when you involve more advanced situations e.g with inheritance, and then you're drilling into singleton classes.
> Ruby immediately calls the method once it's found, whereas Python (generally) doesn't - instead it returns a binding to be called later
Again incorrect, `foo.bar` in Ruby and Python are two very fundamentally different things.
Python returns a binding to a method because it's an attribute accessor; when you throw inheritance into the mix it ends up that that attribute is inherited from the parent class, bound to the instance (which really in python means pass `self` as first argument), and - schematically - adding `()` after that ends up calling that bound method. If there's no attribute that's a no method error. It's all very C-ish and make believe, barely a notch above Go structs and their functions. The closest parallel in Ruby would be `foo.method(:bar).call()`
By contrast Ruby is going to send the :bar message along the inheritance chain, and if someone can respond it's going to invoke the responder's code, and surprise surprise method_missing happens only if it has exhausted inheritance but it's itself a method-slash-message; Oh and by the way the message passing is naturally so lazy that you can actually modify the inheritance chain -in flight- and inject a parent responder right before calling `super`. The whole notion of `binding` is a very concrete construct, way more rich that simply "hey I'm passing self as first argument". It becomes even more strange to "C&al. folks" when you start to involve singleton classes and start to realise weird things like Ruby classes are merely instances of the class Class and it's all instance turtles all the way down and all stupidly simple but you gotta have to wrap your head around it.
I surmise that so many differences and surprises have with Ruby are because most languages have some ALGOL legacy and Ruby is a conceptual heir to Smalltalk (and LISP†); the whole concept of open classes being another one: nothing is ever "finished" in Ruby!
Most of the time you don't have to care about these differences, until you do.
† While code isn't quite S-expr data in Ruby, there are more than enough first-class facilities that you can create and inject code entirely dynamically without resorting to `eval`ing strings.
https://lloeki.github.io/illustrated-ruby/
Each chapter kind of builds up on the previous one. It's still WIP and far from complete but what's there has helped me onboard a few people to Ruby already.
You might be interested in the Classes and Ancestry chapters.
Ruby Instances can have their own methods and override methods separate from their class.
So imagine my delight when I found Ruby in 2005. It took the best of Perl and the best of Smalltalk and gave it a much better syntax than either, plus it had a massively growing community.
Ruby breaks a lot of the rules for what people claim they want (or should be allowed) from a programming language these days, but for me there’s still no more joyful and easy programming language to express my ideas.
Example: Swift and Kotlin can do `Int#times` and don't need message passing to get it done.
Somehow I hadn't thought of the two as similar or related to each other.
Anyway, certainly you can write in this style in Python, since functions are first-class objects. It just has limits on its anonymous functions, and doesn't happen to provide a `times` method on integers (at least partly because the old parser would have struggled with seeing `10.times` and thinking "this should be a floating-point number, but `t` isn't a decimal digit").
> In Python, Java or C++, calling object.method() asks the compiler to find the method in the class and call it.This is incorrect, as noted later. In Python, the lookup occurs at runtime. It also checks the object first, in most cases.
... The writing keeps anticipating my objections and then sort of correcting them and leaving something else not quite right. So trying to edit as I read has been frustrating. The point being, I just don't buy the philosophical difference that the author is trying to draw.
In Python the reason you're dealing with "attributes" rather than "messages" is because functions are first-class objects, which entails that they'll be treated the same way as ordinary data. A method call is two separate steps — the method-lookup and the actual attempt to "call" the result — which also has the implication that you can cache a "bound method" for later use. (By comparison, Ruby lets you "cache" the symbol, which in Python-think looks like some kind of magic string that tries to look itself up as an attribute name on other objects.)
But, I contend, this doesn't meaningfully change how the language is used overall.
> By including Enumerable and by implementing each, we get access to so many methods that we didn’t need to manually implement.
Okay, but in Python `map` and `filter` are just builtin functions, and you can use list comprehensions and `functools.reduce`. It's just a difference between external and internal iteration.
Finally we have the following Scheme Koan:
https://people.csail.mit.edu/gregs/ll1-discuss-archive-html/...
No, it's an absolutely horrible way to "add side-effects" which, usually, is already a horrible idea in its own right.
> Asking an object to iterate over itself allows objects to develop interfaces that dictate how to iterate.
That's true in pretty much any language? And since you need to know which iteration interface you need to use, it's not that much of an advantage.
> And now, when I see: `10.times { |i| puts "i = #{i}" }` I do not see a loop anymore.
Yeah, because it's not a loop: it may or may not run that block 10 times. Seriously, when a programmer's intention is "run something 10 times", the resulting expression arguably should not be "send that something to someone who, hopefully, will execute it 10 times".
Sure, you may not like that mindset, in which case, smalltalk/ruby are ABSOLUTELY not for you. You want something else.
Which is totally fine. Part of the reason behind the Cambrian-explosion of higher level programming paradigms since the 1960's is precisely because there are multiple ways to skin a cat, and different ways resonate with different folks.