6 ms·
It seems you're pretty upset about your experience with Ruby. I'm sorry that's been the case for you. However, in Ruby blocks aren't just about flexibility, mo
by drnewman 1y ago
It seems you're pretty upset about your experience with Ruby. I'm sorry that's been the case for you.
However, in Ruby blocks aren't just about flexibility, more importantly they're about generality. They're not there to resolve an edge case at all (Ruby also has keywords for loops). They're a generalization of control flow that is just slightly less general than continuations. In practical use they provide an alterative to Lisp macros for many use cases.
These were some of the problems Matz was trying to sort out with his design of Ruby--creating a language that was fun and easy to use for day-to-day programming, with as much of the meta-programming power of Lisp and Smalltalk as possible. Blocks are one of his true innovatations that came from trying to balance that tension.
- igouy 1y ago> Blocks are one of his true innovatations … How do they differ from Smallalk blocks? (I don't know.) https://drcuis.github.io/TheCuisBook/Block-syntax.html https://drcuis.github.io/TheCuisBook/Block-syntax.html
- drnewman 1y agoBlocks in Smalltalk (to my understanding) are closures. Blocks in Ruby are closures that also bring the call stack they were created in with them. One way to think of about it is this: anonymous functions as originally implemented in early Lisps are code as an object, closures are code with its lexical environment as an object. You can think of a Ruby block as code with its lexical environment and its call stack as an object. So they don't just handle return differently than closures, they have access to the call stack of the site where they're created like a continuation. This is why they handle return differently, but this is just one of the things that falls out from that. It also comes with other control flow features like "redo", "retry", "next", "rescue", "finally", and others. These are all call stack control (control flow) conveniences, just like return is. All of them can be thought of as being abstractions built on top of continuations (just ask a Scheme hacker). Originally Ruby was basically a Lisp without macros, but with continuations, a Smalltalk like object system and a lot of syntactic affordances inspired by Perl, and other languages. Blocks are one of the conveniences built on top of the Lispy semantics. Note that I'm explaining how blocks work as an abstraction (vidarh below explains how they work as a concretion, as implemented in MRI).
- deleted 1y ago[deleted]
- igouy 1y ago> … other control flow features like "redo", "retry", "next", "rescue", "finally", and others. At-a-glance afaict Smalltalk provides those features too, so I would guess Smalltalk blocks may have access to the call stack too? https://drcuis.github.io/TheCuisBook/The-Debugger.html https://drcuis.github.io/TheCuisBook/The-Debugger.html
- drnewman 1y agoYes Smalltalk has continuations. So it can do all of those things as well. But I don't think they're explicitly tied to blocks like they are in Ruby. This really isn't a problem for Smalltalk since it's not as syntax oriented as Ruby. The invovation is to have those features tied to convenient syntax.
- igouy 1y agoSo is the innovation to make something that was available in Lisps / Smalltalks, available within the different constraints of Ruby. (I should check how Smalltalk blocks behave.)
- igouy 1y agofwiw "Efficient Implementation of Smalltalk Block Returns" https://wirfs-brock.com/allen/things/smalltalk-things/efficient-implementation-smalltalk-block-returns https://wirfs-brock.com/allen/things/smalltalk-things/effici...
- drnewman 1y agoI’ll check it out. Thank you.
- drnewman 1y agoI would say more broadly the innovation was two fold: 1) to make these features available in a syntactic form that would seem more familiar to programmers and 2) the powerful insight that when combined with Smalltalk style meta programming you can have a language that on the surface seems very conventional but underneath is just as powerful as Smalltalk or Lisp. Although I would say he didn’t get 100% there although that this point Ruby isn’t too far from that. These are ideas that I think are worth trying to take even further. In fact, I’ve been experimenting with that.
- deleted 1y ago[deleted]
- chowells 1y agoNo, return exiting the enclosing function scope from a block is the special case that's there to resolve an edge case. Blocks should be nothing special. They're anonymous functions that capture the environment mutably. The only new part is all the special bits added to handle the weird edge cases that they're trying to pretend don't exist.
- HellzStormer 1y agoI disagree that it's just there to handle edge cases. It's a useful generalization. I think the "Building an intuition" section of my blog post[1] makes a good case for that. When dealing with loops, you have 3 nested constructs interacting: a wrapping function, a loop statement and the loop's body; and you have 3 keywords to choose where the flow of the code goes. return returns from the wrapping function break leaves the loop statement next / continue leaves loop's body When dealing with blocks or anonymous functions, it's instead 3 nested "functions" that are interacting: a wrapping function, a called function and an anonymous functions (or block). Ruby's blocks, let you use the same 3 keywords to choose where the flow of the code goes. return returns from the wrapping function (ex: my_func) break returns from the called function (ex: each, map) next returns from the block Quite consistent. But since we are talking about functions instead of statements (loop), return values are also involved. Allowing both break and next to provide a return value fits well in that model and is quite useful. The 3 keywords are basically return, but they have different targets. [1] https://maxlap.dev/blog/2022/02/10/what-makes-ruby-blocks-great.html https://maxlap.dev/blog/2022/02/10/what-makes-ruby-blocks-gr...
- dragonwriter 1y ago> No, return exiting the enclosing function scope from a block is the special case that's there to resolve an edge case. What is the edge case? It seems to be there so that Ruby enumeration methods can provide the behavior expected of looping statements (which is kind of necessary if you want the looping statements to just be semantic sugar for enumeration methods so that they can work correctly with any enumerable.) > Blocks should be nothing special. "Special" compared to what?