やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

無名ブロック(anonymous block)によってRuboCopがコケるようになった

2023年3月31日をもってRuby2.7がEOL(EndOfLife)となりました。安らかにお眠りください...🪦

Ruby | endoflife.date

業務でRubyを触り始めたのが、ちょうど2.3~2.7系の時だったので感慨深いものがあります。
Ruby2.7のEOLに伴って、プロダクトのレポジトリ内でRuby2.7に関連する記載がある箇所がないかと調べていたところ、RuboCopのTargetRubyVersionが2.7となっていることを発見しました。Ruby自体は3.1.2を使っていたので、単純な「対応忘れかな」と思いTargetRubyVersionを3.1.2に更新したところ、以下のエラーが発生するようになりました。

foo/bar.rb:23:58: C: [Correctable] Naming/BlockForwarding: Use anonymous block forwarding. (https://rubystyle.guide#block-forwarding)
  def hoge(fuga, &block)
                                                         ^^^^^^

どうやらRuby3.1から無名ブロック(anonymous block)という機能が追加されており、RuboCopがデフォルト設定ではブロック引数を定義する際は無名ブロックを使うことを推奨しているようです。

# bad
def foo(&block)
  bar(&block)
end

# good
def foo(&)
  bar(&)
end

無名ブロックが推奨されるのマジか

個人的には無名ブロックが推奨されるのにはかなり驚いています。
別にブロック名が省略されていなくても良いですし、無名ブロックの引数名からユーザーにどのようなブロックを期待しているかを伝えることもできるはずです。無名関数も変数に束縛する際に、変数名を省略することはありません。高階関数として関数を受け取る関数側も同様です。

こんな書き方は見たことがない

const _ = () => console.log("anonymous!");
const f = (__) => __();

_(); // anonymous!
f(_); // anonymous!

RubyMasterを見てみる

なぜこの決定がなされたのか気になって調べてみたところ、無名ブロック(anonymous block)についてRubyMasterにてディスカッションしているスレッドを発見しました。

bugs.ruby-lang.org

内容についてざっと目を通した感じ、無名ブロックが受け入れられたのは以下の理由からでした。

  • ブロック引数を定義すると、多くの場合&block&procと記載している
  • 表記が単純となり、無意味な変数が必要なくなる

なるほど...。
自分も言われてみるとブロック引数を定義するとほとんどの場合に&blockと書いていたので、納得できる部分もあります。しかし別のコメントでEregon (Benoit Daloze)さんが指摘しているように「この書き方は混乱を招くと思います」というのにも非常に賛同できます。

Just my opinion, but I think the shortcut syntax is going to cause more confusion than it would help.
& as argument without a & as a parameter looks very weird to me

https://blade.ruby-lang.org/ruby-core/84260

どちらが正しいというのものではありませんが、最終的にはmatzさんは無名ブロックを受け入れることにしたそうです。

RuboCopのデフォルト値に問題があるかも

先ほどのやりとりから無名ブロックの必要性について「確かにな...」と感じました。
ただしデフォルトでブロック引数を定義する際は「全て無名ブロックを使ってね!」というのは違和感があります。ユーザーの好みによって自由に選択してもらえば良い、つまり個人的にはどちらでも良いと思います。

なのでデフォルト値をEnforcedStyle: anonymous (default)からEnforcedStyle: explicitに変更してほしいですね。
(プロダクトコードを統一することがRuboCopの大きな目的なので難しいでしょうが...)

www.rubydoc.info