Ruby’s magic underscore

I discovered today that Ruby treats underscores a little bit differently when it comes to variable names.

Suppose, for the sake of argument, that we have a set of data that looks like this:

people = {
  "Alice" => ["green", "alice@example.com"],
  "Bob"   => ["brown", "bob@example.com"]
}

and we want to ignore eye colour and age and convert each element to a simple array containing name and email address. We might do this:

people.map { |name, fields| [name, fields.last] }

but it’s not very clear what fields.last is. Ruby has some fairly powerful destructuring assignment, so we can reach inside the structure with parentheses to make things more explicit:

people.map { |name, (eye_color, email)| [name, email] }

That’s better, but that unused variable is noise in this context. It doesn’t add to our understanding of what the code does.

In many languages (Haskell, Go, Clojure, and plenty more), it’s conventional to use an underscore to represent an unused variable. This is also recommended by Ruby style guides: 1, 2.

With that in mind, we can write:

people.map { |name, (_, email)| [name, email] }

and be fairly satisfied. But what if we’ve more elements to ignore?

people = {
  "Alice" => ["green", 34, "alice@example.com"],
  "Bob"   => ["brown", 27, "bob@example.com"]
}

No problem. Just re-use the underscore:

people.map { |name, (_, _, email)| [name, email] }

You can’t do it with any variable, though, at least in Ruby 1.9. It only works with variables that are called _:

people.map { |name, (x, x, email)| [name, email] }
# SyntaxError: (eval):2: duplicated argument name

The reason for this is found in Ruby’s parser, in shadowing_lvar_gen. All the normal checks for duplication are skipped iff the variable name consists of exactly one underscore.

Multiple underscores work in Ruby 1.8, too, but for a different reason: Ruby 1.8 doesn’t care about duplicated arguments in block parameters.

Meanwhile, neither 1.8 nor 1.9 care about duplication in multiple assignments:

a, a, b = 1, 2, 3
a, a, b = [1, 2, 3]

Both forms work without error (although I wouldn’t recommend relying on the value of a).

(Thanks to Ben for finding the pertinent line in parse.y.)

Comments

Skip to the comment form

  1. Gavin Laking

    Wrote at 2012-09-22 19:53 UTC using Unknown browser on Mac OS X:

    Nice write up, I wasn’t aware of is trick either. Thanks!
  2. Pradyumna Dandwate

    Wrote at 2012-09-22 20:49 UTC using Safari 536.26.14 on Mac OS X:

    Ruby surprises me every time! It conceals so many awesome things behind a simple and intuitive syntax. Thanks for writing this down.
  3. John Tantalo

    Wrote at 2012-09-23 01:49 UTC using Chrome 21.0.1180.89 on Mac OS X:

    Python has the same syntax,

    >>> (_, a) = (1, 2)
    >>> a
    2

    And perl has a similar syntax,

    (undef, $a) = (1, 2);
    print $a;
    2
  4. Adnan

    Wrote at 2012-09-23 07:34 UTC using Firefox 15.0.1 on Windows 7:

    Nice article.
  5. Tony

    Wrote at 2012-09-23 08:15 UTC using Chrome 21.0.1180.89 on Linux:

    nice~~
  6. Robert Klemme

    Wrote at 2012-09-23 20:59 UTC using Firefox 15.0.1 on Windows Vista:

    There’s also ”*”:

    irb(main):001:0> people = {
    }irb(main):002:1* “Alice” => [“green”, “alice@example.com”],
    irb(main):003:1* “Bob” => [“brown”, “bob@example.com”]
    irb(main):004:1> }

    irb(main):008:0> people.map {|name, (*, email)| [name, email]}
    => [[“Alice”, “alice@example.com”], [“Bob”, “bob@example.com”]]

    irb(main):009:0> people = {
    irb(main):010:1* “Alice” => [“green”, 34, “alice@example.com”],
    irb(main):011:1* “Bob” => [“brown”, 27, “bob@example.com”]
    irb(main):012:1> }
    => {“Alice”=>[“green”, 34, “alice@example.com”], “Bob”=>[“brown”, 27, “bob@example.com”]}

    irb(main):013:0> people.map {|name, (*, email)| [name, email]}
    => [[“Alice”, “alice@example.com”], [“Bob”, “bob@example.com”]]
  7. Paul Stamatiou

    Wrote at 2012-09-24 04:26 UTC using Chrome 21.0.1180.89 on Mac OS X:

    You can also use underscore in irb / rails console to get the last returned value; similar to bash !!.

    ex:

    1.9.3-p0-perf :003 > [1,2,3]
    => [1, 2, 3]
    1.9.3-p0-perf :004 > _.first
    => 1

    Comes in handy quite often when hacking around in rails console.
  8. kiela

    Wrote at 2012-09-26 08:17 UTC using Firefox 15.0.1 on Linux:

    @Robert Klemme

    Single ’*’ works correctly, however, double ’*’ does not work at all:

    kiela@rico:~/workspace/adbuyer$ irb
    >> people = {
    ?> “Alice” => [“green”, “alice@example.com”],
    ?> “Bob” => [“brown”, “bob@example.com”]
    >> }
    => {“Alice”=>[“green”, “alice@example.com”], “Bob”=>[“brown”, “bob@example.com”]}
    >> people.map { |name, (*, email)| [name, email] }
    => [[“Alice”, “alice@example.com”], [“Bob”, “bob@example.com”]]
    >> people = {
    ?> “Alice” => [“green”, 34, “alice@example.com”],
    ?> “Bob” => [“brown”, 27, “bob@example.com”]
    >> }
    => {“Alice”=>[“green”, 34, “alice@example.com”], “Bob”=>[“brown”, 27, “bob@example.com”]}
    >> people.map { |name, (*, *, email)| [name, email] }
    SyntaxError: (irb):10: syntax error, unexpected tSTAR
    people.map { |name, (*, *, email)| [name, email] }
    ^
    (irb):10: syntax error, unexpected ’)’, expecting ’=’
    people.map { |name, (*, *, email)| [name, email] }
    ^
    (irb):10: syntax error, unexpected ’}’, expecting $end
    from /home/kiela/.rvm/rubies/ruby-1.9.3-p194/bin/irb:16:in `’
    >>
  9. Paul Battley

    Wrote at 2012-09-26 10:08 UTC using Chrome 21.0.1180.89 on Linux:

    kiela: No, it doesn’t work: the asterisk collects all the otherwise-unassigned elements, so using more than one would be ambiguous.

    I’ve written about destructuring assignment in more detail in a follow-up post.
  10. kiela

    Wrote at 2012-09-27 15:02 UTC using Firefox 15.0.1 on Linux:

    @Paul Battley
    Now I get it, thanks a lot ;)
  11. Matheus Moreira

    Wrote at 2012-09-27 16:40 UTC using Safari (Mobile) 533.1 on Android:

    mu is too short and I answered Andrew Marshall’s question about this subject on StackOverflow a while back.

    It’s truly an interesting feature, one I didn’t know about until I attempted to answer that question. I prefer to keep the variable names, though. Especially when they’re part of a destructuring assignment.
  12. Sijo K George

    Wrote at 2012-12-11 10:01 UTC using Firefox 11.0 on Linux:

    Thanks for sharing this information
  13. carlos

    Wrote at 2013-06-26 12:19 UTC using Safari 537.43.58 on Mac OS X:

    But other than code style, does it help in performance? does it make the application faster by using the _ instead of an normal variable name?

Leave a comment

Please read the comment guidelines before posting. Comments are Gravatar-enabled. Your email address will not be published.

To prove that you’re human, type human in the Bot check field.

Trying to post some program output or a long code sample? Please use a paste service and link to it instead.