In Progress
Unit 1, Lesson 21
In Progress

Ignore Arguments

Video transcript & code

A couple of episodes back, we looked at a file format for a file called .netrc, which stores usernames and passwords for remote hosts. Here is an example .netrc file. The actual .netrc syntax can be more complicated than this, but for the purpose of today's example we'll assume a simplified syntax where each entry is on a single line, and each entry lists a machine name, then a username, and then a password. Each value is preceded by a keyword.

machine rubytapas.com login avdi password xyzzy
machine example.org login marvin password plugh

Supposing we were to write our own parser for this simplified .netrc format. We might do it like this: read each line, then split the line into columns separated by whitespace. Once we have the columns, since we know where the values we are interested in will be located, we can assign variables for machine, login, and password based on their column index.

open('.netrc').lines do |line|
  columns  = line.split
  machine  = columns[1]
  login    = columns[3]
  password = columns[5]
  puts "#{machine}: #{login} / #{password}"
end
# >> rubytapas.com: avdi / xyzzy
# >> example.org: marvin / plugh

This works fine, but the column indexes don't make for very communicative code. Knowing, as we do from recent episodes, that we can implicitly splat arrays into multiple variables on assignment, we might be tempted to use a single assignment statement to break out the machine, login, and password variables.

open('.netrc').lines do |line|
  machinekey, machine, loginkey, login, passwordkey, password  = line.split
  puts "#{machine}: #{login} / #{password}"
end
# >> rubytapas.com: avdi / xyzzy
# >> example.org: marvin / plugh

This makes it easy to visually understand the positional nature of the data columns just be looking at the code. But now we have a bunch of useless variables ending in -key which exist simply to be placeholders for the field keywords in the .netrc format.

Our next thought is to clarify that we don't care about any of those fields by using the variable name ignored for all of them.

open('.netrc').lines do |line|
  ignored, machine, ignored, login, ignored, password  = line.split
  puts "#{machine}: #{login} / #{password}"
end

This does the trick, until we decide to factor out the part of the logic that reads the file, into a separate method which takes a block. Suddenly, Ruby throws a fit about duplicate argument names and won't run our program.

def netrc_entries
  open('.netrc').lines do |line|
    yield(*line.split)
  end
end

netrc_entries do |ignored, machine, ignored, login, ignored, password|
  puts "#{machine}: #{login} / #{password}"
end
# ~> -:7: duplicated argument name
# ~> netrc_entries do |ignored, machine, ignored, login, ignored, password|
# ~>                                             ^
# ~> -:7: duplicated argument name
# ~> netrc_entries do |ignored, machine, ignored, login, ignored, password|
# ~>                                                             ^

Ruby is actually trying to do us a favor here; usually, if we duplicate an argument name, it means we've mis-typed something. In this case we really mean it, though. So what do we do? We could go back to using separate variable names for each of the ignored columns, but that seems really messy.

As it turns out, Ruby has a very special variable name for just this situation. If we replace our ignored variable with a variable called simply _, Ruby relaxes its usual rules about duplicate argument names. The underscore is intended for just this situation, where we have arguments that we want to ignore.

def netrc_entries
  open('.netrc').lines do |line|
    yield(*line.split)
  end
end

netrc_entries do |_, machine, _, login, _, password|
  puts "#{machine}: #{login} / #{password}"
end
# ~>                                                             ^
# >> rubytapas.com: avdi / xyzzy
# >> example.org: marvin / plugh

Using the underscore, we end up with a version of the code that reads quite nicely as a visual diagram of the parts of the input we want to use, and the parts we don't care about.

In the next episode we'll look at how to ignore many unwanted arguments at once. Until then, happy hacking!

Responses