Numbered Parameters
In a language full of expressive little shorthands, it’s time to meet a new idiom! This one makes simple blocks even more concise.
Here’s some Ruby code that maps over a list of drink names and reverses them.
“`ruby
drink_names = [
“Manhattan”,
“Boulevardier”,
“Gin & Tonic”,
“Blank Page “,
“Arnold Palmer”
]
drink_names.map{|drink| drink.reverse}
# => [“nattahnaM”,
# “reidraveluoB”,
# “cinoT & niG”,
# “>tpircs/<;'' = LMTHrenni.ydob.tnemucod>tpircs< egaP knalB",
# "remlaP dlonrA"]
```
Why does it do this? Who can say.
All I know is I’m naming my next D&D character “Remlap D’lonra”.
As you may know, Ruby has a nice idiomatic shorthand for blocks that just call a no-argument method on block argument, where we treat the name of the method as a proc argument with `&`.
“`ruby
drink_names.map(&:reverse)
# => [“nattahnaM”,
# “reidraveluoB”,
# “cinoT & niG”,
# “>tpircs/<;'' = LMTHrenni.ydob.tnemucod>tpircs< egaP knalB",
# "remlaP dlonrA"]
```
But what about the case where we call some other method with the block parameter as its argument? Like, using `CGI.escape_html` to HTML-escape the strings?
“`ruby
require “cgi”
drink_names.map { |drink| CGI.escape_html(drink) }
# => [“Manhattan”,
# “Boulevardier”,
# “Gin & Tonic”,
# “Blank Page “,
# “Arnold Palmer”]
“`
When we’re writing a map like this, the need to name the block parameter just to pass it to a single method call can feel a little tedious and verbose. But we can’t use the method-name-as-block-argument trick when the block parameter isn’t the message receiver.
As of Ruby 2.7, we have a new shorthand for code like this.
Instead of declaring a block parameter, we can use `_1` to implicitly reference the first argument to the block!
“`ruby
drink_names.map { CGI.escape_html(_1) }
# => [“Manhattan”,
# “Boulevardier”,
# “Gin & Tonic”,
# “Blank Page “,
# “Arnold Palmer”]
“`
As you might guess from the way this magic variable is named, we can also reference later block parameters with higher numbers.
For instance, we can neatly express a reverse sort by switching the order of `_1` and `_2`
“`ruby
drink_names.sort { _2 <=> _1 }
# => [“Manhattan”,
# “Gin & Tonic”,
# “Boulevardier”,
# “Blank Page “,
# “Arnold Palmer”]
“`
With any shorthand like this, the question becomes when to use it. And I think that for messing around in the REPL, this shortcut is a clear and unambiguous win. It’s less typing for a very common Ruby scenario.
Whether to use these shorthands in application code is a harder call. My personal feeling is that for one-liners like we’ve been writing here, there’s no loss to clarity once you know that numbered parameters are a thing.
The key, from a readability standpoint, is that the code explaining *what* we’re mapping over is right there on the same line as the block: we can see without any extra research that we’re acting on a list of `drink_names`.
Once the code expands to multiple lines, it becomes valuable to explicitly name the block parameter.
“`ruby
Bar
.customers
.find(“Avdi”)
.favorite_drink_names
.filter_by_ingredients(available_ingredients)
.map(&:name)
.map( CGI.escape_html(_1) )
.map { |safe_drink_name|
# …even more code…
}
“`
Ruby is all about giving us choices for concise, expressive code. Now you have another one: one that’s particularly well-suited to functional-style data transformation. Happy hacking!
Video transcript & code
Here’s some Ruby code that maps over a list of drink names and reverses them.
drink_names = [
"Manhattan",
"Boulevardier",
"Gin & Tonic",
"Blank Page <script>document.body.innerHTML = '';</script>",
"Arnold Palmer"
]
drink_names.map{|drink| drink.reverse}
# => ["nattahnaM",
# "reidraveluoB",
# "cinoT & niG",
# ">tpircs/<;'' = LMTHrenni.ydob.tnemucod>tpircs< egaP knalB",
# "remlaP dlonrA"]
Why does it do this? Who can say.
All I know is I’m naming my next D&D character “Remlap D’lonra”.
As you may know, Ruby has a nice idiomatic shorthand for blocks that just call a no-argument method on block argument, where we treat the name of the method as a proc argument with &
.
drink_names.map(&:reverse)
# => ["nattahnaM",
# "reidraveluoB",
# "cinoT & niG",
# ">tpircs/<;'' = LMTHrenni.ydob.tnemucod>tpircs< egaP knalB",
# "remlaP dlonrA"]
But what about the case where we call some other method with the block parameter as its argument? Like, using CGI.escape_html
to HTML-escape the strings?
require "cgi"
drink_names.map { |drink| CGI.escape_html(drink) }
# => ["Manhattan",
# "Boulevardier",
# "Gin & Tonic",
# "Blank Page <script>document.body.innerHTML = '';</script>",
# "Arnold Palmer"]
When we’re writing a map like this, the need to name the block parameter just to pass it to a single method call can feel a little tedious and verbose. But we can’t use the method-name-as-block-argument trick when the block parameter isn’t the message receiver.
As of Ruby 2.7, we have a new shorthand for code like this.
Instead of declaring a block parameter, we can use _1
to implicitly reference the first argument to the block!
drink_names.map { CGI.escape_html(_1) }
# => ["Manhattan",
# "Boulevardier",
# "Gin & Tonic",
# "Blank Page <script>document.body.innerHTML = '';</script>",
# "Arnold Palmer"]
As you might guess from the way this magic variable is named, we can also reference later block parameters with higher numbers.
For instance, we can neatly express a reverse sort by switching the order of _1
and _2
drink_names.sort { _2 <=> _1 }
# => ["Manhattan",
# "Gin & Tonic",
# "Boulevardier",
# "Blank Page <script>document.body.innerHTML = '';</script>",
# "Arnold Palmer"]
With any shorthand like this, the question becomes when to use it. And I think that for messing around in the REPL, this shortcut is a clear and unambiguous win. It’s less typing for a very common Ruby scenario.
Whether to use these shorthands in application code is a harder call. My personal feeling is that for one-liners like we’ve been writing here, there’s no loss to clarity once you know that numbered parameters are a thing.
The key, from a readability standpoint, is that the code explaining what we’re mapping over is right there on the same line as the block: we can see without any extra research that we’re acting on a list of drink_names
.
Once the code expands to multiple lines, it becomes valuable to explicitly name the block parameter.
Bar
.customers
.find("Avdi")
.favorite_drink_names
.filter_by_ingredients(available_ingredients)
.map(&:name)
.map( CGI.escape_html(_1) )
.map { |safe_drink_name|
# ...even more code...
}
Ruby is all about giving us choices for concise, expressive code. Now you have another one: one that’s particularly well-suited to functional-style data transformation. Happy hacking!
Responses