For this audience it may be worth noting that Ruby’s blocks are closures and are passed to methods either anonymously/implicitly or as a named parameter, may be subsequently passed around to any collaborator object, or otherwise deferred/ignored, have the same range of argument arity as methods and lambdas, can even be formed from (and treated similarly to) lambdas, and are thereby fundamental to Ruby’s claim to being a multiparadigm language even as they also betray the Smalltalk roots.
In addition they have nonlocal return semantics, somewhat like a simple continuation, making them ideal for inline iteration and folding, which is how most new Rubyists first encounter them, but also occasionally a source of surprise and confusion, most notably if one mistakenly conflates return with result. Ruby does separately have callcc for more precise control over stack unwinding, although it’s a little known feature.
> can even be formed from (and treated similarly to) lambdas
They are also used to create lambdas (even the shorthand stabby-lambda syntax desugars to a call to Kernel#lambda with a block.)
> Ruby does separately have callcc for more precise control over stack unwinding, although it’s a little known feature.
callcc is included in CRuby but has been sidelined from Ruby as a language separate from CRuby as an implementation for a while, with Fibers understood to cover the most important use cases for callcc.
As someone who comes from strict languages (the more strict, the better) Ruby blocks are... not fun.
I've seen them used in situations where they are used like a callback, but due to the nature of how you write them, you have no clue whether the variable you're referring to is a local or a global one.
Ruby isn’t strict, no, but that’s by the by, because this doesn’t sound like a problem with blocks or how you write them. It sounds more like a problem with evals i.e. some library or framework misusing them. Blocks are closures and they straightforwardly bind variables and resolve constants/instance variables from the context of their instantiation, and resolve methods similarly because self within the block is from instantiation, when we call them normally with yield or Proc#call. Same goes for implicit contexts used for definition, if your block does a bit of metaprogramming.
If someone plays silly buggers and invokes them under instance_eval or class_exec etc that fiddle with self or definition contexts then some of this goes out the window, but those are special-purpose methods that come papered with red flags. This is typically seen in poorly designed DSLs that are trying too hard to pretend they’re not actually Ruby. If memory serves, the Chef DSL was a prime example in this regard. If the language was stricter, then sure, this wouldn’t be possible. But debugging these cases isn’t super hard either once you know the limited range of culprits, and the fix is always the same: place values in local stack variables to rely on them in a closure.
Using blocks for callbacks is fine. Don’t make assumptions about the semantics of flow control statements that other languages may have imposed on you, i.e. use next and break for explicit block results and local exit instead of return, and don’t eval them.
Back in the day, a lot of people including me reported feeling more comfortable in Ruby after one week than all their other languages with years of experience, as if Ruby just fits your mind like a glove naturally.
I'm glad new people are still having that "Ruby moment"
Ruby is still the first tool I reach for if I need to do something quickly and naturally. People are always surprised at how readable the code comes out even compared to python.
Thank you for reading this.
I'm having fun learning Ruby. I just started working at a company where I use it full time. It's great learning it and I have supportive colleagues who are excited for me. I'm going to write more about Ruby. I have planned about 6 articles in the next few weeks. I hope I get around to them all.
Coming from a language with functions as first class objects, blocks felt a bit limited to me, because it feels as if you almost have functions but not really, and they get inputted by a back door. Used for example to:
let isLarge = a => a>100;
numbers.filter(isLarge)
Blocks let you do the same but without extracting the body as cleanly. Maybe it’s a chronological issue, where Ruby was born at a time when the above wasn’t commonplace?
>When you write 5.times { puts “Hello” }, you don’t think “I’m calling the times method and passing it a block.” You think “I’m doing something 5 times.”
I’m of two minds about this.
On the one hand, I do agree that aesthetically Ruby looks very clean and pleasing. On the other, I always feel like the mental model I have about a language is usually “dirtied” to improve syntax.
The value 5 having a method, and that method being an iterator for its value, is kinda weird in any design sense and doesn’t seem to fix any architectural order you might expect, it’s just there because the “hack” results in pretty text when used.
These magical tricks are everywhere in the language with missing_method and the like, and I guess there’s a divide between programmers’ minds when some go “oh that’s nice” and don’t care how the magic is done, and others are naturally irked by the “clever twists”.
> The value 5 having a method, and that method being an iterator for its value, is kinda weird in any design sense and doesn’t seem to fix any architectural order you might expect, it’s just there because the “hack” results in pretty text when used.
I don't think this is particularly weird, in Ruby at least. The language follows object orientation to its natural conclusion, which is that everything is an object, always. There is no such thing as "just data" in Ruby, because everything is an object. Even things that would just be an `int` in most other languages are actually objects, and so they have methods. The `times` method exists on the Integer classes because doing something exactly an integer number of times happens a lot in practice.
I don’t have an issue with the “everything’s an object” part, because it _is_ consistent, even though it gets a bit trippy when classes are objects as well and they are implementation of a Class class which is an implementation of itself (trickery again!).
The issue is more with this part:
>The `times` method exists on the Integer classes because doing something exactly an integer number of times happens a lot in practice.
It is practical, but it breaks the conceptual model in that it is a hard sell that “times” is a property over the “5” object.
The result is cleaner syntax, I know, but there is something in these choices that still feels continually “hacky/irky” to me.
With similar justification, you could argue that blocks should have a method `times` taking an integer, because repeating a block an integer number of times happens a lot in practice. I’d even argue that it is conceptually closer to blocks than to integers, and therefore the method belongs on the block. Hence you’d write `{ puts “Hello” }.times 5`.
But blocks are not objects in Ruby, so you can’t do that, and everything actually isn’t an object in Ruby.
Also, it’s even more common to do something depending on whether a condition is true or false, but true and false in Ruby don’t have a method to (not) execute a block on them, and you use a non-OOP `if` instead, so what’s up with that?
`5.times` is not so outlandish, though it would seem better for that to be in a `Loop` library or something (`Loop.times(5) { do some stuff }`).
The `5.days` example that was posted somewhere else in this thread might be a better example. It is not, as far as I can tell, part of Ruby's core library, thank goodness, but it is the kind of thing the methods-as-hacks-for-syntax culture seems to encourage. My question being "why the heck should the number 5 know anything about days? What does 5.days even mean, given that there are various ways to interpret 'a day'?"
This kind of bad design has made its way all over the place. The Java mocking libraries that my coworkers like to use are full of it. Long chains of method calls that appear like they're trying to 'look like English' but make no damn sense, so you have to dig into each method call and what kind of thing it returns to understand what this chain actually means.
Blocks are fundamentally different from functions due to the control flow: `return` inside a block will return the outer method, not the block. `break` stops the whole method that was invoked.
This adds some complexity in the language, but it means that it’s far more expressive. In Ruby you can with nothing but Array#each write idiomatic code which reads very similar to other traditional languages with loops and statements.
More specifically blocks (and "proc"'s) return from the defining scope. This is just a minor clarification, but it matters, because if you pass a block down from where it is defined, and the block calls "return" it will still not just exit from the method where it was called, but from the method where it was defined.
This can sometimes be useful: A calling method can pass down a block or proc to control if/when it wants an early return.
Basically Ruby has two types of closures:
* A return in a lambda returns to the calling scope. So basically, it returns to after where the "call" method is invoked.
* A return in a block or a proc returns from the scope in which it was defined (this is also why you get LocalJumpError if you try to return a block or a proc, but not a lambda to the method calling the one where the block or proc is defined).
When you name a block, you get a Proc object, same as you get when you take the value of a lambda or proc.
In practice, that blocks in MRI are not Proc objects already is just an implementation detail/optimisation. I have a long-standing hobby project to write a Ruby compiler, and there a "proc" and a bare block are implemented identically in the backend.
If you are familiar with a true object-oriented language like Smalltalk (rather than the watered-down form of OO in C++, Java, etc.), an integer like 5 having methods makes sense because it (like everything else) is an object. Objects in Ruby aren't just window dressing -- they are its core.
But then Ruby only goes half way, not unlike the "watered-down form" in your term. Why is `#times` a method of Integer, but `#if` (or `#ifTrue`) not a method of booleans like in Smalltalk? Ruby does the same cherry picking from Smalltalk like everybody else, just different cherries.
When looking at Ruby, it feels like the simple examples are all nice and clean but then the weird details start to appear and the language feels more hacky than others (like Ned Flander's house in Simpsons S08E08).
Interestingly, in the Lambda Calculus, where everything is a function, a standard representation for a natural number n (i.e. a whole number >= 0), is indeed a function that 'iterates' (strictly, folds/recurses) n times.
The "aesthetically pleasing" aspect of blocks is not mutually exclusive with real, first-class functions! Ruby is really more functional than that. Ruby has both lambas and method objects (pulled from instances). For example, you can write:
let isLarge = a => a>100;
as a lambda and call via #call or the shorthand syntax .():
I find the .() syntax a bit odd, so I prefer #call, but that's a personal choice. Either way, it mixes-and-matches nicely with any class that has a #call method, and so it allows nice polymorphic mixtures of lambdas and of objects/instances that have a method named 'call'. Also very useful for injecting behavior (and mocking behavior in tests).
Additionally, you can even take a reference to a method off of an object, and pass them around as though they are a callable lambda/block:
class Foo
def bar = 'baz'
end
foo_instance = Foo.new
callable_bar = foo_instance.method(:bar)
callable_bar.call
# => 'baz'
This ability to pull a method off is useful because any method which receives block can also take a "method object" and be passed to any block-receiving method via the "block operator" of '&' (example here is passing an object's method to Array#map as a block):
class UpcaseCertainLetters
def initialize(letters_to_upcase)
@letters_to_upcase = letters_to_upcase
end
def format(str)
str.chars.map do |char|
@letters_to_upcase.include?(char) ? char.upcase : char
end.join
end
end
upcase_vowels = UpcaseCertainLetters.new("aeiuo").method(:format)
['foo', 'bar', 'baz'].map(&upcase_vowels)
# => ['fOO', 'bAr', 'bAz']
This '&' operator is the same as the one that lets you call instance methods by converting a symbol of a method name into a block for an instance method on an object:
That is interesting! I haven't explored Procs much, since I use ruby for a shared codebase at work and I was originally a bit afraid of trying to push unidiomatic ideas in the codebase.
In your experience, is it ok to use Procs for example for extraction of block methods for cleanliness in refactors? or would I hit any major roadblocks if I treated them too much like first-class functions?
Also, is there any particular Rails convention to place collections of useful procs? Or does that go a bit against the general model?
Ruby is my favorite language. There is no other language with same ergonomics. I dont understand hype around ts and all frameworks that changes every week.
I discovered Ruby (through Rails) about twenty years ago on the dot. Coming from Perl and PHP it took me a while, but I remember the moment when I had the same realisation you did.
I still love this language to bits, and it was fun to relive that moment vicariously through someone elses eyes. Thanks for writing it up!
I'm glad folks are having fun reading this. I want to write a few more articles, particularly dissecting the internals of Ruby and how amazing it feels.
Ruby is basically what happens when Smalltalk and Perl (compare the command line and the "$"-variables for example) has a baby and it inexplicably looks much better than both parents.
An interesting thing about Smalltalk and Ruby blocks is that they aren't just anonymous functions/lambdas, right? i.e. if you 'return' / '^' in a block, it's the context around the block that you return from, not just the block itself? That's what struck me about both of them when I was used to thinking in basic Lisp terms.
I believe the underlying behaviour of Ruby blocks is one of those mechanics that isn't talked about that much for newcomers, they just get used to how Ruby code look like when they see Rails, Cucumber or RSpec
Blocks and method_missing is one of those things in Ruby is what makes it so powerful! I remember watching a talk where someone was able to run JS snippets on pure Ruby just by recreating the syntax. That proves how powerful Ruby is for creating your own DSL
It's also a double edged sword and something you have to be careful with on collaborative codebases. Always prefer simplicity and readability over exotic and neat tricks, but I understand the difficulty when you have access to such a powerful tool
There are legitimate uses of method_missing (though often you'd want to call define_method dynamically instead for performance), but they tend to be when you use an object as a proxy for something (say a remote API) where you genuinely can't know the set of messages the object might need to be able to receive.
Ruby is still so good to read and hack things with. It is a shame that it is not so popular and you know the reasons why. I still wish with such a good DSL friendly structure, it should have become an IaC de-facto standard language or used in some niche where I could use it freely without question.
The biggest thing holding Ruby back is lack of gradual typing imo. I honestly think javascript is a better fit for IaC. Not only is already the language of the web (everyone has to know some javsacript) but JSDOC is supported by most IDEs giving it gradual typing. Many people don't realize jsdoc is typescript and is a full replacement.
Nowadays I like to reach for Julia for quick one-off scripts and webscraping. It has a beautiful and accessible syntax with built-in gradual typing. I would love to see it more widely adopted in the IaC world
In addition they have nonlocal return semantics, somewhat like a simple continuation, making them ideal for inline iteration and folding, which is how most new Rubyists first encounter them, but also occasionally a source of surprise and confusion, most notably if one mistakenly conflates return with result. Ruby does separately have callcc for more precise control over stack unwinding, although it’s a little known feature.
Dead Comment
They are also used to create lambdas (even the shorthand stabby-lambda syntax desugars to a call to Kernel#lambda with a block.)
> Ruby does separately have callcc for more precise control over stack unwinding, although it’s a little known feature.
callcc is included in CRuby but has been sidelined from Ruby as a language separate from CRuby as an implementation for a while, with Fibers understood to cover the most important use cases for callcc.
I've seen them used in situations where they are used like a callback, but due to the nature of how you write them, you have no clue whether the variable you're referring to is a local or a global one.
This makes debugging incredibly hard.
If someone plays silly buggers and invokes them under instance_eval or class_exec etc that fiddle with self or definition contexts then some of this goes out the window, but those are special-purpose methods that come papered with red flags. This is typically seen in poorly designed DSLs that are trying too hard to pretend they’re not actually Ruby. If memory serves, the Chef DSL was a prime example in this regard. If the language was stricter, then sure, this wouldn’t be possible. But debugging these cases isn’t super hard either once you know the limited range of culprits, and the fix is always the same: place values in local stack variables to rely on them in a closure.
Using blocks for callbacks is fine. Don’t make assumptions about the semantics of flow control statements that other languages may have imposed on you, i.e. use next and break for explicit block results and local exit instead of return, and don’t eval them.
Deleted Comment
Back in the day, a lot of people including me reported feeling more comfortable in Ruby after one week than all their other languages with years of experience, as if Ruby just fits your mind like a glove naturally.
I'm glad new people are still having that "Ruby moment"
1. Someone scoffs at you for writing ruby instead of python / go / javascript / kotlin
2. Then they read the code
3. Then they install ruby
It's an amazing language and remains my go-to for anything that doesn't need protocol stuff.
let isLarge = a => a>100;
numbers.filter(isLarge)
Blocks let you do the same but without extracting the body as cleanly. Maybe it’s a chronological issue, where Ruby was born at a time when the above wasn’t commonplace?
>When you write 5.times { puts “Hello” }, you don’t think “I’m calling the times method and passing it a block.” You think “I’m doing something 5 times.”
I’m of two minds about this.
On the one hand, I do agree that aesthetically Ruby looks very clean and pleasing. On the other, I always feel like the mental model I have about a language is usually “dirtied” to improve syntax.
The value 5 having a method, and that method being an iterator for its value, is kinda weird in any design sense and doesn’t seem to fix any architectural order you might expect, it’s just there because the “hack” results in pretty text when used.
These magical tricks are everywhere in the language with missing_method and the like, and I guess there’s a divide between programmers’ minds when some go “oh that’s nice” and don’t care how the magic is done, and others are naturally irked by the “clever twists”.
I don't think this is particularly weird, in Ruby at least. The language follows object orientation to its natural conclusion, which is that everything is an object, always. There is no such thing as "just data" in Ruby, because everything is an object. Even things that would just be an `int` in most other languages are actually objects, and so they have methods. The `times` method exists on the Integer classes because doing something exactly an integer number of times happens a lot in practice.
The issue is more with this part:
>The `times` method exists on the Integer classes because doing something exactly an integer number of times happens a lot in practice.
It is practical, but it breaks the conceptual model in that it is a hard sell that “times” is a property over the “5” object.
The result is cleaner syntax, I know, but there is something in these choices that still feels continually “hacky/irky” to me.
But blocks are not objects in Ruby, so you can’t do that, and everything actually isn’t an object in Ruby.
Also, it’s even more common to do something depending on whether a condition is true or false, but true and false in Ruby don’t have a method to (not) execute a block on them, and you use a non-OOP `if` instead, so what’s up with that?
I beg to differ. What object does the method `puts` belong to? Why do you not call puts with its objects name?
Ruby has a concept of mixins (Golang interfaces), these are not objects. Neither is `puts`
The `5.days` example that was posted somewhere else in this thread might be a better example. It is not, as far as I can tell, part of Ruby's core library, thank goodness, but it is the kind of thing the methods-as-hacks-for-syntax culture seems to encourage. My question being "why the heck should the number 5 know anything about days? What does 5.days even mean, given that there are various ways to interpret 'a day'?"
This kind of bad design has made its way all over the place. The Java mocking libraries that my coworkers like to use are full of it. Long chains of method calls that appear like they're trying to 'look like English' but make no damn sense, so you have to dig into each method call and what kind of thing it returns to understand what this chain actually means.
This adds some complexity in the language, but it means that it’s far more expressive. In Ruby you can with nothing but Array#each write idiomatic code which reads very similar to other traditional languages with loops and statements.
This can sometimes be useful: A calling method can pass down a block or proc to control if/when it wants an early return.
Basically Ruby has two types of closures:
* A return in a lambda returns to the calling scope. So basically, it returns to after where the "call" method is invoked.
* A return in a block or a proc returns from the scope in which it was defined (this is also why you get LocalJumpError if you try to return a block or a proc, but not a lambda to the method calling the one where the block or proc is defined).
When you name a block, you get a Proc object, same as you get when you take the value of a lambda or proc.
In practice, that blocks in MRI are not Proc objects already is just an implementation detail/optimisation. I have a long-standing hobby project to write a Ruby compiler, and there a "proc" and a bare block are implemented identically in the backend.
E.g. 3:
(f, x) => f(f(f(x)))
Additionally, you can even take a reference to a method off of an object, and pass them around as though they are a callable lambda/block:
This ability to pull a method off is useful because any method which receives block can also take a "method object" and be passed to any block-receiving method via the "block operator" of '&' (example here is passing an object's method to Array#map as a block): This '&' operator is the same as the one that lets you call instance methods by converting a symbol of a method name into a block for an instance method on an object: And doing similar, but with a lambda:In your experience, is it ok to use Procs for example for extraction of block methods for cleanliness in refactors? or would I hit any major roadblocks if I treated them too much like first-class functions?
Also, is there any particular Rails convention to place collections of useful procs? Or does that go a bit against the general model?
I still love this language to bits, and it was fun to relive that moment vicariously through someone elses eyes. Thanks for writing it up!
Blocks and method_missing is one of those things in Ruby is what makes it so powerful! I remember watching a talk where someone was able to run JS snippets on pure Ruby just by recreating the syntax. That proves how powerful Ruby is for creating your own DSL
It's also a double edged sword and something you have to be careful with on collaborative codebases. Always prefer simplicity and readability over exotic and neat tricks, but I understand the difficulty when you have access to such a powerful tool
Method missing is a different beast altogether. I would probably avoid it nowadays.
Nowadays I like to reach for Julia for quick one-off scripts and webscraping. It has a beautiful and accessible syntax with built-in gradual typing. I would love to see it more widely adopted in the IaC world
There is: https://sorbet.org/