Help us understand the problem. What is going on with this article?

サンプルコードでわかる!Ruby 2.7の主な新機能と変更点 Part 1 - 番号指定パラメータ(numbered parameter)

はじめに

Rubyは毎年12月25日にアップデートされます。
Ruby 2.7については2019年11月23日にpreview3がリリースされました。

Ruby 2.7.0-preview3 リリース

この記事ではRuby 2.7で導入される変更点や新機能について、サンプルコード付きでできるだけわかりやすく紹介していきます。

ただし、Ruby 2.7は多くの新機能や変更点があり、1つの記事に収まらないのでいくつかの記事に分けて書いていきます。
本記事で紹介するのは番号指定パラメータ(numbered parameter)です。

本記事の情報源

本記事は以下のような情報源をベースにして、記事を執筆しています。

動作確認したRubyのバージョン

本記事は以下の環境で実行した結果を記載しています。

$ ruby -v
ruby 2.7.0preview3 (2019-11-23 master b563439274) [x86_64-darwin19]

フィードバックお待ちしています

本文の説明内容に間違いや不十分な点があった場合は、コメント欄や編集リクエスト等で指摘 or 修正をお願いします🙏

それでは以下が本編です!

ブロックの仮引数として番号指定パラメータが試験的に導入された

Ruby 2.7ではブロックの仮引数として番号指定パラメータ(numbered parameter)が試験的に導入されました。
これにより、|s| のように明示的に引数名を指定する代わりに、_1 のような連番でブロックの仮引数を受け取ることができます。

# 番号指定パラメータを使わない場合
%w(1 20 300).map { |s| s.rjust(3, '0') }
#=> ["001", "020", "300"]

# 番号指定パラメータを使う場合
%w(1 20 300).map { _1.rjust(3, '0') }
#=> ["001", "020", "300"]
# 番号指定パラメータを使わない場合
[1, 2, 3, 4].inject(0) { |memo, n| memo + n }
#=> 10

# 番号指定パラメータを使う場合
[1, 2, 3, 4].inject(0) { _1 + _2 }
#=> 10

(Ruby初心者さん向けの補足)
上のコードに登場する%w()という構文は、文字列の配列を作成するための構文です。

%w(1 20 300)
# ↑のコードは、↓と書いているのと同じ
["1", "20", "300"]

割り当てられていない連番はnil

割り当てられない連番を指定するとnilが返ります。

# 9番目の仮引数は何も割り当てられないのでnil
%w(1 20 300).map { _9 }
#=> [nil, nil, nil]

使用できる連番は_1から_9まで

使用できる連番は_1から_9までです。
_10のような連番を指定するとNameErrorが発生します。

%w(1 20 300).map { _10 }
#=> NameError (undefined local variable or method `_10' for main:Object)

_1〜_9のようなローカル変数を宣言していると警告が出る

番号指定パラメータを使っているかどうかにかかわらず、_1_9のような変数名を宣言していると、コードを読み込んだタイミングで警告が出ます。

# 番号指定パラメータと同名の変数を宣言すると警告が出る
_1 = "999"
#=> warning: `_1' is used as numbered parameter

ですので、既存のコードをRuby 2.7環境で動かすと、場合によってはこのような警告が出る可能性があります。

同名のローカル変数があるとブロック内でも変数が優先される

ブロックの外で同名の変数が宣言されている場合、ブロック内では番号指定パラメータではなく、ブロックの外で宣言されたローカル変数として参照されます。

# 番号指定パラメータと同名の変数を宣言しておく
_1 = "999"

# _1はローカル変数の"999"として扱われる
%w(1 20 300).map { _1.rjust(3, '0') }
#=> ["999", "999", "999"]

ただし、同名のローカル変数がブロックの後ろで宣言されていた場合はこの限りではありません。

# _1が番号指定パラメータとして機能する
%w(1 20 300).map { _1.rjust(3, '0') }
#=> ["001", "020", "300"]

# 同名のローカル変数をブロックの後ろで宣言する
_1 = "999"

同名のメソッドがあると番号指定パラメータが優先される

_1のような名前のメソッドが定義されている場合は、番号指定パラメータが優先されます。

# 番号指定パラメータと同名のメソッドを定義しておく
def _1
  '123'
end

# _1は番号指定パラメータとして扱われる
%w(1 20 300).map { _1.rjust(3, '0') }
#=> ["001", "020", "300"]

# _1() または self._1 とすればメソッドを明示的に呼び出せる
%w(1 20 300).map { _1().rjust(3, '0') }
#=> ["123", "123", "123"]

%w(1 20 300).map { self._1.rjust(3, '0') }
#=> ["123", "123", "123"]

ちなみに、Ruby 2.6だと番号指定パラメータの構文が導入されていないため、メソッドの_1が呼ばれます。

def _1
  '123'
end

# Ruby 2.6の場合はメソッドの_1が呼ばれる(Ruby 2.7と挙動が異なる)
%w(1 20 300).map { _1.rjust(3, '0') }
#=> ["123", "123", "123"]

Ruby 2.6と2.7では挙動が異なりますが、この場合は警告も出ないため、運が悪いと「Ruby 2.7に上げたら、なぜかちゃんと動かなくなった!」というトラブルが起きるかもしれません。(まあ、こんなコードを書いている人は滅多にないと思いますが・・・)

ネストしたブロックで番号指定パラメータを使う際の注意点

番号指定パラメータをネストしたブロックの中で使おうとするとsyntax errorが発生します。

sum = 0
[*1..4].each_slice(3) do
  # 外側のブロックで番号指定パラメータを使う
  _1.each do
    # 内側のブロックでも番号指定パラメータを使おうとするとエラーになる
    sum += _1
  end
end
#=> numbered parameter is already used in (SyntaxError)
#   outer block here
#           sum += _1
#   ^~~~~~~~~~~~~~~~~

外側のブロックだけ、もしくは内側のブロックだけで使うのはOKです。

sum = 0
[1, 2, 3, 4].each_slice(3) do |arr|
  arr.each do
    # 内側のブロックでだけ番号指定パラメータを使う
    sum += _1
  end
end
sum #=> 10

従来のブロック仮引数と混在させると構文エラーになる

次のコードのように、従来のブロック仮引数と混在させると構文エラーが発生します。

# 従来のブロック仮引数 |memo, n| と、番号指定パラメータ _1 が混在すると構文エラー
[1, 2, 3, 4].inject(0) { |memo, n| _1 + n }
#=> ordinary parameter is defined (SyntaxError)

ただし、明示的に番号指定パラメータと同名のブロック仮引数を指定した場合は構文エラーになりません。

# 番号指定パラメータと同名のブロック仮引数を使えば構文エラーにならない
[1, 2, 3, 4].inject(0) { |_1, n| _1 + n }
#=> 10

コラム:濫用厳禁!?番号指定パラメータの使いどころ

タイプ量が減るので一見便利に見える番号指定パラメータですが、個人的には「濫用厳禁、用量と用法を守って使いましょう」な新機能だなと感じます。

なぜなら連番だとコードを書いた人の意図がまったく見えないので、可読性が落ちるからです。
(単純に「連番は脳への負担が大きい」という問題もあります)

# 引数名を見れば「1, 2, 3, 4の値が渡されるのはnの方だな」と推測できる
[1, 2, 3, 4].inject(0) { |memo, n| memo + n }

# ん?どっちがどっちだ!?わからん!!
[1, 2, 3, 4].inject(0) { _1 + _2 }

irbなどでサクッと実行結果を確認したりするときに使うのは便利だと思いますが、実務で書く寿命の長いコードの場合はなるべく使用を控えた方が良い気がします。(そもそもRuby 2.7では「試験的な新機能」の扱いですので!)

まとめ

本記事ではRuby 2.7で試験的に導入された番号指定パラメータ(numbered parameter)を紹介しました。

冒頭でも説明したように「サンプルコードでわかる!Ruby 2.7の主な新機能と変更点」はいくつかの記事に分けて執筆します。
新しい記事を書いたらQiitaの「変更通知」でお知らせしますので、いちはやくキャッチしたい方は本記事のストックをよろしくお願いします!

2019.12.10追記:パターンマッチの記事(前編・後編)を書きました

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away