Numbered Parameters

Unit Progress
0% Complete
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 &amp; Tonic",
#     "Blank Page &lt;script&gt;document.body.innerHTML = &#39;&#39;;&lt;/script&gt;",
#     "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 &amp; Tonic",
#     "Blank Page &lt;script&gt;document.body.innerHTML = &#39;&#39;;&lt;/script&gt;",
#     "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!

 

Scroll to Top