This was definitely written by a pythonist! If I tried to write it, as a rubyist, I'm sure I'd get some things about python wrong. (I find it notable how few people there are that are actually familiar with both).
The standard alternative to `for` in ruby does involve `each` and blocks... but definitely doesn't involve defining a custom `each` method on your class... That is a specialty thing that most developers have probably done rarely. Let alone defining it in terms of `for`, which is just weird!
But the basic principle stated "Instead of passing data back to the for loop (Python) you pass the code to the data (Ruby)" -- is more or less accurate.
blocks -- syntactic support in the language for cleanly passing a single in-line defined lambda/closure object as an argument -- are possibly the thing that are most special to ruby.
> Python builds on for-like constructs for all kinds of processing; Ruby pushes other kinds of data processing work to methods.
OK, maybe, although not totally sure what you mean.
> Ruby keeps going with its methods-first approach, except instead of each we have a new set of methods commonly implemented on collections, as below:
Um. I'm not sure where the author is getting this. It is certainly possible, as shown, but definitely not common to implement `select` or `map` or other methods provided by Enumerable directly on your custom class. It is a bit more common to implement `each` alone and let the `Enumerable` mixin provide the rest. But even that I'm not sure how "common" I'd call it.
> Ruby, however, inverts this. Ruby puts object-orientation as the foundation of the pyramid. Ruby contains the messy procedural world in blocks, letting objects work with those procedural blocks.
OK, true. The author is getting the details wrong, but I guess their overall picture is still right?
> blocks -- syntactic support in the language for cleanly passing a single in-line defined lambda/closure object as an argument -- are possibly the thing that are most special to ruby.
Too bad ruby stopped short of doing the trivial obvious thing and just making blocks be regular values. Instead the language is complicated by special syntax and functions for sending and receiving blocks, and bizarrely limited by the inability to do anything with a block literal other than send it.
Blocks were so close to being good. They managed the triple flip with a double twist, but they couldn't stick the landing. It's not quite a faceplant at the end, but it clearly shows how much better they could have been.
What do you mean by inability to do anything with a literal? You can capture it, turn into a variable, turn it into a "lambda" (make next, break, return local), use it like callbacks are used in any other language. Blocks are a bit special in that they go in their own slot for message sends (method calls) which allows the syntax to be unambiguous and the yield keyword allows for optimized calls to passed in blocks because methods that don't reference a block as data don't have to worry about the block outliving the stack frame.
No there’s special methods which turns blocks into objects, and there’s a syntax to do that, but blocks aren’t objects unless you specifically do one of those things. The idea you’re replying to is that they’d be objects always.
> Too bad ruby stopped short of doing the trivial obvious thing and just making blocks be regular values.
Abstractly, I kind of see the point, in practice, I don't see it makes much difference, and given Ruby’s two flavors of function-like objects, it seems to work out for the best.
> the inability to do anything with a block literal other than send it.
But sending lets you do anything else you’d want to to do with it. Specifically, to get either of the flavors of callables, you pass it to “proc” or “lambda”, and then use the result.
Not having every block be a separate value makes it easier for the Ruby VM to optimize such code without escape analysis (which is something that's pretty hard to do in this language).
Don't have to allocate the container => don't need to use the heap one more time. Nor access the block's code through that indirection.
Same thing with methods: they aren't objects, but you can create an object pointing to a method any time you need one.
> Too bad ruby stopped short of doing the trivial obvious thing and just making blocks be regular values. Instead the language is complicated by special syntax and functions for sending and receiving blocks, and bizarrely limited by the inability to do anything with a block literal other than send it.
Blocks are syntactical structures. Your statement is like saying that the parens and commas that are part of the argument list should be "regular values".
I'm not sure what you want to do with a "block literal". If you want to do something with the closure represented by the block, then reify the block into an object to pass it around, wrap it, introspect etc.
I think a lot of confusion about blocks in Ruby is really inconsistent terminology. Using "block" to mean the syntactic expression of a closure, i.e. part of the method call syntax I think helps to disambiguate the syntactical nature of closures from the closure reified into an object that you can pass around, call, etc. (i.e. an instance of the Proc class)
> Blocks are syntactical structures. Your statement is like saying that the parens and commas that are part of the argument list should be "regular values".
Well, no. Know what else is a syntactic structure? Numeric and string literals. People would never have touched the language in the first place if Ruby required you to write code like
x = String("abc")
y = Integer(123)
But people bend over backwards to explain why
z = { |x| x + 1 }
Is bad and shouldn't be allowed.
What?
The whole block vs proc thing is an artificial distinction. It doesn't add value, it removes it.
(Yeah, I know about the difference in how they handle return. This strikes me as an incredibly ad-hoc way to address something they could have solved a lot more elegantly.)
> But people bend over backwards to explain why “z = { |x| x + 1 }” is bad and shouldn't be allowed.
Really? I’ve never seen anyone bend over backward to explain it as bad, mostly just that that's the way it is, there are tradeoffs either way, and its not worth changing.
> The whole block vs proc thing is an artificial distinction
Presumably, you mean lambda vs. proc. (Both of which are defined using blocks, and procs are what using & syntax in a function signature causes a passed block to be reified into.)
But, yes, all distinctions are creations of humans, especially all distinctions within human creations like, say, programming languages. So “artificial distinction” is a meaningless descriptor when we are talking about things in a programming language.
Ruby doesn't have functions. It only has methods. That blocks in MRI happen to be implemented without reifying an object is an optimisation. You can only ever obtain a reference to said block by reifying it into a Proc instance.
Letting you obtain some kind of raw reference to a block that isn't a method on an object would make blocks unlike every other value in Ruby.
Hah, as a Pythonista, I'd say it was definitely written by a Rubyist :D
That initial example would usually be, in Python:
class Stuff:
def __init__(self):
self.a_list = [1,2,3,4]
def __iter__(self):
for value in self.a_list:
yield value
Basically, iterators and generators are native constructs that for loop operates on in Python (lists, which are actual "data", are simply special-case optimized instances of those).
Instead of calling iterators/generators "data", I'd call them wrapper control-flow constructs that `for` really operates on which provide amazing syntactic power in Python.
Ruby, from the examples given, seems quite similar, except that the "syntactic sugar" is somewhat inverted. I don't think it makes for a huge difference, but I don't have any Ruby experience.
I was not trying to go for the shortest code, but to show idiomatic code where you could easily do something else instead of just returning same values (otherwise, there is no value in wrapping a list with a class at all).
With both `iter(self.a_list)` or `yield from` you'd have to add a list comprehension in there to process each element, and with no Ruby-like blocks in Python, that limits what one can do.
But they are both definitely good approaches to highlight!
The article has some misunderstandings about idiomatic ruby imo.
> Ruby keeps going with its methods-first approach, except instead of each we have a new set of methods commonly implemented on collections, as below:
And then you show map, select, etc being re-implemented.
But once you have "each" defined, you'd just include "Enumerable" and get all the others for free.
As a separate point, I almost never implement each on a custom object in ruby. There are probably "library code" cases where it's appropriate, but in day to day work it should be rare. Typically I'd put objects in an ordinary array instead.
Nit: Ruby style guide (and most experienced code I've seen) never uses parens to invoke a method with no args.
As a separate point, I almost never implement
each on a custom object in ruby. There are
probably "library code" cases where it's appropriate
Likewise. I've worked with Ruby fulltime since 2014 and never done it in actual project code, only in book exercises, and I'm not sure I've seen it in any gems I've dove into, though I've never poked around in ActiveRecord.
Like you said, I can't think of too many reasons why one would want to implement #each -- on a daily basis I'm just putting various objects into arrays, hashes, etc.
I tend to write very "boring" Ruby. Ruby gives you lots of ways to get wacky, but IMO the default best practice would be to keep it simple and avoid the cute stuff unless you really have a reason.
I didn't think the author was saying you should always implement a .each method. I thought the implementations were for demonstration purposes, that .each is a method like any other that can be overridden.
There are other details that are wrong. Ruby does have iterators — except they're called enumerators. The standard implementation is called Enumerator:
Enumerators behave similarly to generators in Python thanks to utilities like #to_enum:
class X
def each
yield 1
yield 2
yield 3
end
end
>> x = X.new.to_enum
>> x.each
#<Enumerator: #<X:0x00007fd30c93dfa0>:each>
>> x.next
1
[etc.]
Ruby's for loops actually use enumerators, just like Python. It's just not used as much, for cultural reasons; most devs these days favour Ruby's data-oriented inversion of control.
Python and Ruby are quite different when writing code, but have almost the exact same technical abilities and limitations in the grand scheme of things. I've always felt there's little reason to learn both.
Ruby is way more capable and expressive than Python. There is so much more you can do with Ruby metaprogramming and don't get me started on Python's feeble lambdas which bear the mark of a BDL imposing his distaste for functional programming. Python is the VHS of programming - widely adopted but technically inferior. Ruby appeals to devs who value elegance of design. Take Sonic Pi (https://sonic-pi.net), for example - I can't imagine sam Aaron producing anything like this in Python. The DSL is everything in this app as with Rails.
Yes exactly, you're not really going to learn anything (syntax aside) by picking up the second, unless you needed it for work reasons or whatever there's surely several other paradigmatically different languages that will be more interesting/instructive to learn.
I don't think jrochkind is correct at all, if you want to make a library that has a datastructure that you want to implement custom iteration on, then it is most definitely idiomatic ruby to implement `each` for it. Your article was spot on in my opinion.
That said, in the almost 15 years I've been doing Ruby as my preferred programming language, I think I can count the amount of times I implemented a custom `each` method on one, maybe two hands.
As an example of how `each` is idiomatic, consider the Enumerable mixin: https://ruby-doc.org/core-3.0.2/Enumerable.html if you implement `each` on your class that mixin gives you all those methods (such as select) for free.
> syntactic support in the language for cleanly passing a single in-line defined lambda/closure object as an argument
I think Perl has this too? Or, well, I should say that Perl allows you to do anything, so even if Perl doesn't let you do it, you can still do it in Perl.
That `do` is a special syntactic thing avaialble for passing a single inline-defined closure arg. (You can pass one held in a reference instead of inline-defined, but it actually takes an extra somewhat obtuse step -- the syntax is optomized for inline-defined).
This "affordance" says "Yes, we make it really easy to do this, the stdlib does it a lot, please consider doing it all the time in your own code", which goes along with some of what the OP is discussing. Why we write `collection.each {|something| something}` (the braces are an alternate way to pass a block) instead of `for something in collection do...`
I think the crucial difference is that a block can control the flow of the enclosing frame, somewhat analogously to python context managers. For example
def get_widget
with_lock do
return @widget # returns from the method call
end
end
def update_interesting_gadget
@gadgets.each do |g|
g.with_lock do
if g.is_theone?
g.update
break # breaks the enclosing each's while loop
end
end
end
The main advantages of Ruby blocks over that approach are:
- they have special control flow that interacts with the method call or surrounding function. ie. calling `break` in `something` can early return from `someMethod`, or calling `return` will return from the function containing the `someMethod` call (blocks use `next` to return from them)
- due to using separate syntax / being a separate language construct, there is far better ergonomics in the presence of vargs or default values
Take this contrived example for instance:
def some_method(a = 42)
b = yield
puts "Hey #{a} #{b}"
end
some_method do
break
end
In JS you would have to something horrible like this:
const breakSomeMethod = {}; // Could alternatively use an exception
function someMethod(one, two) {
var f, a;
if (typeof one == 'function') {
f = one;
a = 42;
} else {
a = one;
f = two;
}
var b = f();
if (b === breakSomeMethod) {
return;
}
console.log(`Hey ${a} ${b}`)
}
someMethod(() => breakSomeMethod);
Scala doesn't really have a name for this, it simply permits you to provide a block in lieu of a parenthesized argument list. Given that blocks are expressions:
val a = { val x = 2; x + 1 }
the syntax you are describing is just a function-valued block.
The standard alternative to `for` in ruby does involve `each` and blocks... but definitely doesn't involve defining a custom `each` method on your class... That is a specialty thing that most developers have probably done rarely. Let alone defining it in terms of `for`, which is just weird!
But the basic principle stated "Instead of passing data back to the for loop (Python) you pass the code to the data (Ruby)" -- is more or less accurate.
blocks -- syntactic support in the language for cleanly passing a single in-line defined lambda/closure object as an argument -- are possibly the thing that are most special to ruby.
> Python builds on for-like constructs for all kinds of processing; Ruby pushes other kinds of data processing work to methods.
OK, maybe, although not totally sure what you mean.
> Ruby keeps going with its methods-first approach, except instead of each we have a new set of methods commonly implemented on collections, as below:
Um. I'm not sure where the author is getting this. It is certainly possible, as shown, but definitely not common to implement `select` or `map` or other methods provided by Enumerable directly on your custom class. It is a bit more common to implement `each` alone and let the `Enumerable` mixin provide the rest. But even that I'm not sure how "common" I'd call it.
> Ruby, however, inverts this. Ruby puts object-orientation as the foundation of the pyramid. Ruby contains the messy procedural world in blocks, letting objects work with those procedural blocks.
OK, true. The author is getting the details wrong, but I guess their overall picture is still right?