LoginSignup
17
4

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

Last updated at Posted at 2023-12-17

はじめに

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

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

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

2024年にリリースされる(2023年じゃないよ!)のRuby 3.4では1_1の代わりにitが使えるようになる予定です。

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

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

# 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" という記事で詳しく説明していたので、原著者の了承をいただいた上でこの記事の日本語訳をお届けします。

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

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

Original: Exploring `it` default block param warning in Ruby 3.3 by Lucian Ghinda

先日のRuby開発者ミーティングitがRuby 3.4で追加されることが了承されました。

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

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

非推奨警告

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

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

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

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

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

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

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

以下はその例です。

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

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

 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は通常、文字列の引数と一緒に呼ばれるからです。

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を呼び出しているからです)

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

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で定義されているメソッドの全リストは以下の通りです。

# 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がデフォルトのブロックパラメータとして了承されたことを私は喜んでいます。(来年の)Ruby 3.4を使ってコードを書くのがとても楽しみです。

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

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


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

Rubyの最新情報を毎週お届けするニュースレター「Short Ruby News」にご登録ください。Rubyの学習リソースについては、rubyandrails.infoをご覧ください。また、Ruby.socialLinkedinTwitterでもRubyとRailsについて投稿しています。私のYouTubeチャンネルでは、Rubyに関する短い動画を配信しています。

(翻訳はここまで)

訳者による補足

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

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

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 はブロックパラメータとして使われていないことが明らかなので、警告が出ないものと思われます。

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

  2. 参考:"The warning should be printed always (even without $VERBOSE)"

17
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
4