【翻訳】Ruby 3.3で発生する`it`デフォルトブロックパラメータ警告について

## はじめに

Ruby 2.7では、ブロックの仮引数として番号指定パラメータが導入されました。

```ruby
[1, 2, 3].map { _1 * 10 }
#=> [10, 20, 30]

# 上のコードは以下のコードと同じ
[1, 2, 3].map { |n| n * 10 }
```

2024年にリリースされる（2023年じゃないよ！）のRuby 3.4では[^1]、`_1`の代わりに`it`が使えるようになる予定です。

[^1]: Rubyは毎年12月25日に新バージョンがリリースされます。このため、Ruby 3.3は2023年12月25日に、Ruby 3.4は2024年12月25日にそれぞれリリースされる予定です。

```ruby
# Ruby 3.4（2024年12月25日リリース予定）
[1, 2, 3].map { it * 10 }
#=> [10, 20, 30]
```

この前準備として、2023年リリースのRuby 3.3では、ブロックの内部で`it`がレシーバも引数もブロックもなしに呼ばれた場合に警告を出すようになります（この警告はRubyの実行オプションによらず常に出力されます[^2]）。

[^2]: 参考："[The warning should be printed always (even without $VERBOSE)](https://github.com/ruby/dev-meeting-log/blob/master/2023/DevMeeting-2023-11-30.md#feature-18980-re-reconsider-numbered-parameters-it-as-a-default-block-parameter-k0kubun)"

```ruby 
# Ruby 3.3（2023年12月25日リリース予定）
def it = 10
[1, 2, 3].map { it * 10 }
#=> warning: `it` calls without arguments will refer to the first block param in Ruby 3.4; use it() or self.it
#=> [100, 100, 100]
```

この内容について、Lucian Ghinda氏が "[Exploring \`it\` default block param warning in Ruby 3.3](https://allaboutcoding.ghinda.com/exploring-it-default-block-param-warning-in-ruby-33)" という記事で詳しく説明していたので、[原著者の了承をいただいた上で](https://twitter.com/lucianghinda/status/1736384410600002024)この記事の日本語訳をお届けします。

なお、翻訳は日本語の読みやすさを重視して、意訳している部分があります。あらかじめご了承ください。

# 【翻訳】Ruby 3.3で発生する`it`デフォルトブロックパラメータ警告について

_Original: [Exploring \`it\` default block param warning in Ruby 3.3](https://allaboutcoding.ghinda.com/exploring-it-default-block-param-warning-in-ruby-33) by Lucian Ghinda_

[先日のRuby開発者ミーティング](https://github.com/ruby/dev-meeting-log/blob/master/2023/DevMeeting-2023-11-30.md#feature-18980-re-reconsider-numbered-parameters-it-as-a-default-block-parameter-k0kubun)で`it`がRuby 3.4で追加されることが了承されました。

この準備として、Ruby 3.3では`it`がレシーバも引数もブロックもなしに呼ばれた場合に警告を出すようになります。

私はこの内容に関する簡単な動画を作成したので、動画を見たい方はこちらをご覧ください。テキストバージョンを好む方はこのまま読み進めてください。

https://www.youtube.com/watch?v=DnMNn13k-rg

## 非推奨警告

この内容を確認するために、シンプルなコードを書いてみましょう。`it`という名前のメソッドを定義し、それをブロックの内部で呼び出します。

```ruby
puts RUBY_VERSION

def it = "works"

def run(&block) = yield

puts run { it }
```

このコードをRuby 3.2.2で実行すると、以下のような実行結果になります。

```
3.2.2
works
```

しかし、このコードをRuby 3.3.0.rc1で実行すると、次のような実行結果になります。

```
example_deprecation_warning.rb:7: warning: `it` calls without 
arguments will refer to the first block param in Ruby 3.4; 
use it() or self.it
3.3.0
works
```

## この警告の修正方法

もしあなたが`it`という名前のメソッドを定義していて、今後もブロック内でレシーバも引数もブロックパラメータもなしに呼び出したい場合は、括弧を追加してください。

```diff_ruby
-puts run { it }
+puts run { it() }
```

またはレシーバを追加してください。

```diff_ruby
-puts run { it }
+puts run { self.it }
```

## `it`をローカル変数名として使用した場合

`it`をローカル変数の名前として定義した場合は問題ありません。

以下はその例です。

```ruby
puts RUBY_VERSION

def log(&block) = puts yield

def run
  it = 2

  log { it }
end

run
```

Ruby 3.3.0.rc1でこのコードを実行してもエラーは表示されません。出力結果は以下のようになります。

```
3.3.0
2
```

ですが、もしこのローカル変数を同じ名前でメソッドとして抽出すると……

```diff_ruby
 puts RUBY_VERSION
 
 def log(&block) = puts yield
+
+def it = 2
+
 def run
-  it = 2

   log { it }
 end

 run
```

警告が出力されます。

```
it_as_local_variable.rb:8: warning: `it` calls without arguments 
will refer to the first block param in Ruby 3.4; use it() or self.it
3.3.0
2
```

## RSpec

RSpecでは`it`がよく使われます。しかし、RSpecではほとんど（いや、滅多に）問題はおきません。なぜなら`it`は通常、文字列の引数と一緒に呼ばれるからです。

```ruby
require "rspec"

describe Galaxy do
  context "when created with a name and number of stars" do
    it "should hold the correct name and star count" do
      galaxy = Galaxy.new("Milky Way", 100_000_000_000)
      expect(galaxy.name).to eq("Milky Way")
      expect(galaxy.number_of_stars).to eq(100_000_000_000)
    end
  end
end
```

上のコードは警告が **表示されません。**

（訳注：文字列を省略して `it do ... end` のように書いた場合も警告は表示されません。なぜならブロック付きで`it`を呼び出しているからです）

しかし、こんなコードを書くと……

```ruby
require "rspec"

describe Galaxy do
  subject { Galaxy.new("Milky Way", 100_000_000_000).stars }

  context "When there are no known stars" do
    it
  end
end
```

Ruby 3.3.0.rc.1では警告が発生します。

```
explore-it/rspec_example/spec/more_galaxy_spec.rb:8: warning: `it` 
calls without arguments will refer to the first block param in 
Ruby 3.4; use it() or self.it
*

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Galaxy When there are no known stars 
     # Not yet implemented
     # ./spec/more_galaxy_spec.rb:8


Finished in 0.0074 seconds (files took 0.06474 seconds to load)
1 example, 0 failures, 1 pending
```

RSpecでは`it`のエイリアスが定義されています。この問題を修正するには、以下のエイリアスのいずれかを使用してください。

- `specify`
- `example`

なお、RSpec coreで定義されている[メソッドの全リスト](https://github.com/rspec/rspec-core/blob/f273314f575ab62092b2ad86addb6a3c93d6041f/lib/rspec/core/example_group.rb#L157C1-L157C1)は以下の通りです。

```ruby
# Defines an example within a group.
define_example_method :example
# Defines an example within a group.
# This is the primary API to define a code example.
define_example_method :it
# Defines an example within a group.
# Useful for when your docstring does not read well off of `it`.
# @example
#  RSpec.describe MyClass do
#    specify "#do_something is deprecated" do
#      # ...
#    end
#  end
define_example_method :specify

# Shortcut to define an example with `:focus => true`.
# @see example
define_example_method :focus,    :focus => true
# Shortcut to define an example with `:focus => true`.
# @see example
define_example_method :fexample, :focus => true
# Shortcut to define an example with `:focus => true`.
# @see example
define_example_method :fit,      :focus => true
# Shortcut to define an example with `:focus => true`.
# @see example
define_example_method :fspecify, :focus => true
# Shortcut to define an example with `:skip => 'Temporarily skipped with xexample'`.
# @see example
define_example_method :xexample, :skip => 'Temporarily skipped with xexample'
# Shortcut to define an example with `:skip => 'Temporarily skipped with xit'`.
# @see example
define_example_method :xit,      :skip => 'Temporarily skipped with xit'
# Shortcut to define an example with `:skip => 'Temporarily skipped with xspecify'`.
# @see example
define_example_method :xspecify, :skip => 'Temporarily skipped with xspecify'
# Shortcut to define an example with `:skip => true`
# @see example
define_example_method :skip,     :skip => true
# Shortcut to define an example with `:pending => true`
# @see example
define_example_method :pending,  :pending => true
```

## まとめ

`it`がデフォルトのブロックパラメータとして[了承された](https://bugs.ruby-lang.org/issues/18980#note-47)ことを私は喜んでいます。（来年の）Ruby 3.4を使ってコードを書くのがとても楽しみです。

Ruby 3.3の警告機能は潜在的な名前の衝突を検知するのに役立ちます。とはいえ、この問題はごくまれにしか発生しないでしょう。

大半の開発者はスムーズに移行できるはずです。RSpecのテストは通常、文字列の引数を付けて`it`を使うので、警告対象になりません。よって、修正不要です。万一、警告が発生したとしても、`it`の代わりに`specify`や`example`を使えば、警告をなくせます。

----

この記事をお楽しみいただけましたか？

Rubyの最新情報を毎週お届けするニュースレター「[Short Ruby News](https://shortruby.com/)」にご登録ください。Rubyの学習リソースについては、[rubyandrails.info](https://rubyandrails.info/)をご覧ください。また、[Ruby.social](https://ruby.social/)、[Linkedin](https://www.linkedin.com/in/lucianghinda/)、[Twitter](https://twitter.com/lucianghinda)でもRubyとRailsについて投稿しています。[私のYouTubeチャンネル](https://www.youtube.com/@shortruby)では、Rubyに関する短い動画を配信しています。

（翻訳はここまで）

## 訳者による補足

記事の中では明示的なサンプルコードがありませんでしたが、警告が発生する条件は、ブロックの内部で`it`がレシーバも引数もブロックもなしに呼ばれた場合に加えて、「ブロックパラメータがない」という条件も必要です。

たとえば、`[1, 2, 3].map { it }` は警告が出ますが、 `[1, 2, 3].map { |n| n + it }` だと警告は出ません。

```ruby 
def it = 10

# ブロックパラメータがないので警告が出る
[1, 2, 3].map { it * 10 }
#=> warning: `it` calls without arguments will refer to the first block param in Ruby 3.4; use it() or self.it
#=> [100, 100, 100]

# ブロックパラメータ（ここではn）があるので警告が出ない
[1, 2, 3].map { |n| n + it }
#=> [11, 12, 13]
```

`[1, 2, 3].map { it * 10 }` の場合、Ruby 3.4では暗黙的なブロックパラメータとして `it` に1, 2, 3という値が代入されるように仕様が変わりますが、 `[1, 2, 3].map { |n| n + it }` ではRuby 3.3でも3.4でも明示的なブロックパラメータである `n` に1, 2, 3が代入されます。このため、 `[1, 2, 3].map { |n| n + it }` の `it` はブロックパラメータとして使われていないことが明らかなので、警告が出ないものと思われます。