6 ms·
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
by jrochkind1 5y ago
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?
- kache_ 5y agoas a rubyistpythonista citizen, i agree
- deleted 5y ago[deleted]
- pas 5y agoIt's the norm in Scala too. And it's a bit overdone in (older) JS (libraries).
- jrochkind1 5y agoI don't know scala, but the notable thing about ruby vs JS is how ruby provides _syntactic_ support for passing an inline-defined function. In JS: someObj.someMethod(function(something) { something }); Compare to ruby with the block arg: someObj.someMethod do |something| something end 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...`
- hnov 5y agoI 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
- joelbluminator 5y agoSo Anonymous Ruby blocks act like (or are) Procs (which return from the parent method), not like lambdas, would you say that?
- Brybry 5y agoIn modern JS wouldn't you use an arrow function? someObj.someMethod(something => something);
- didroe 5y agoThe 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);
- jrumbut 5y agoI 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.
- greenie_beans 5y agothat's how i read it too, for the purpose of the technical demonstration
- chowells 5y ago> 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.
- gwright 5y ago> 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)
- chowells 5y ago> 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.)
- Buttons840 5y agoPython 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.
- OJFord 5y agoYes 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.
- baash05 5y agoI'd add elixir to that list too. For the most part, the language differences are not really capitalised on.
- cutler 5y agoRuby 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 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.
- smnc 5y agoNot sure how they compare feature-wise, but take a look at FoxDot: https://github.com/Qirky/FoxDot https://github.com/Qirky/FoxDot
- cutler 5y agoAfter experiencing Sam Aaron's Overtone - a Clojure REPL for SuperCollider - this does not exactly inspire: d1 >> play(P["x( x) "].palindrome().zip("---[--]").zip(P[" o "].amen()))
- softwaredoug 5y ago> but definitely doesn't involve defining a custom `each` method on your class... Author here! Thanks for the feedback. I suspected as much, and was more or less writing it to be illustrative. Though I agree I am bad at Ruby :)
- tinco 5y agoI 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 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.
- baash05 5y agoYeah.. totally.. 10 years, I've never seen a custom 'each'
- necovek 5y agoHah, 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.
- softwaredoug 5y agoAuthor here, I think this would have made the post better for sure, especially to contrast how `yield` does the opposite thing in each language
- jonahx 5y agoThe 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.
- JohnBooty 5y agoAs 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.
- throwaway984393 5y ago> 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.
- atombender 5y agoThere are other details that are wrong. Ruby does have iterators — except they're called enumerators. The standard implementation is called Enumerator: >> [1, 2, 3].each => #<Enumerator: [1, 2, 3]:each> >> e = [1, 2, 3].each => #<Enumerator: [1, 2, 3]:each> >> e.next => 1 >> e.next => 2 >> e.next => 3 >> e.next Traceback (most recent call last): 2: from (irb):17 1: from (irb):17:in `next' StopIteration (iteration reached an end) 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.