Some Smalltalk about Ruby Loops

tech.stonecharioteer.com

85 points by birdculture 7 days ago


top_sigrid - 8 hours ago

Like Alan Kay himself said, it wasn't about objects, but about messaging. [0]

[0] https://lists.squeakfoundation.org/pipermail/squeak-dev/1998...

matltc - 3 hours ago

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 - 4 hours ago

Leaving the comfort of lisp, I have started to wonder: why are all the looping facilities in other languages so awful?

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!"

munificent - 3 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:

https://journal.stuffwithstuff.com/2013/01/13/iteration-insi...

https://journal.stuffwithstuff.com/2013/02/24/iteration-insi...

throwaway106382 - 5 hours ago

7 minutes of Pharroh Smalltalk for Rubyists: https://www.youtube.com/watch?v=HOuZyOKa91o

stonecharioteer - 7 days ago

Author here, I'd already shared it here.

https://news.ycombinator.com/item?id=45644349

dominicrose - 7 hours ago

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 ";")

pansa2 - 8 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.

skywhopper - 8 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.

lazyvar - 8 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.

zahlman - 5 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.

stonecharioteer - 6 hours ago

Damn, I'm a fulltime Ruby blogger now?

Joker_vD - 8 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".