Ruby: ブロック変数の「シャドウイング」はシャドウイングなのかが気になって調べた(社内勉強会)

誤りや見落としがありましたら@hachi8833までお知らせください🙇。

きっかけ

オライリー「プログラミング言語Ruby」に以下の記述がありました。

同書p250より

ここで言う「シャドウイング」が気になりました。Rubyでシャドウイングというと、次に書いたようなブロック変数(ここではx)がブロックの外のローカル変数をシャドウイングすることを指すと思っていたので。

元々インスタンス変数のシャドウイングについて書くつもりでしたが、先にブロック変数のシャドウイングについて調べることにしました。

Rubyの「ブロック変数によるシャドウイング」

以下はシャドウイングでよく引き合いに出されるブロック変数のシャドウイングです。

x = 42
3.times { |x| puts "x is #{x}" }

ブロック{ |x| puts "x is #{x}" }内のxは、その外の名前のかぶったローカル変数x = 42とは別物になるので、ブロック内でxに何をしても外のローカル変数は変更されません。

ローカル変数と名前がぶつかっていなければ、ブロック内からブロック外のローカル変数に普通にアクセスできます。

ちなみにブロック変数のシャドウイングはよくないとされていて、Rubyでも警告が表示されます。シャドウイングによって正常に動作はしますが、意図が不明確になる(ローカル変数を変更したいのかしたくないのかがわかりにくい)のがよくないからだと思われます。

$ ruby -w variable_shadowing.rb
variable_shadowing.rb:2: warning: shadowing outer local variable - x
variable_shadowing.rb:1: warning: assigned but unused variable - x
x is 0
x is 1
x is 2

同書p149でも、これをシャドウイングと表記していました。

週刊Railsウォッチ(20181001)で取り上げたRailsコードの修正でも、まさにこのブロック変数のシャドウイングが解消されていました。

# activerecord/test/cases/connection_adapters/connection_handler_test.rb#L170
        ActiveRecord::Base.configurations = config
-        ActiveRecord::Base.configurations.configs_for.each do |config|
-          assert_instance_of ActiveRecord::DatabaseConfigurations::HashConfig, config
+        ActiveRecord::Base.configurations.configs_for.each do |db_config|
+          assert_instance_of ActiveRecord::DatabaseConfigurations::HashConfig, db_config
        end

一般的な意味の「シャドウイング」

オブジェクト指向言語において、フィールド名、ローカル変数名、パラメータ名が、同じ名前のフィールド名、ローカル変数名、パラメータ名の別の宣言によって、単純名ではアクセスできなくなること。また、メソッド名やラベル名が、それぞれと同じメソッド名やラベル名の別の宣言によって、単純名ではアクセスできなくなること。スコープの重複によって発生する現象であって、隠蔽とは意味が異なる。
シャドーイング(しゃどーいんぐ)とは - コトバンクより(一部強調)

この一般的なシャドウイングの説明では「単純名ではアクセスできなくなること」とありますが、Rubyのブロック変数でシャドウイングが発生すると、名前がかぶっている限り外部のローカル変数にブロック内からアクセスする手段がないので、Rubyのブロック変数のシャドウイングは、一般的な意味での変数のシャドウイングとは少し異なるのかなと思いました。

追記(2018/11/03)

直接フィードバックをいただきました。ありがとうございます!やっとすっきりしました🙇。

追記(2018/11/06)

これも初めて知りました😳。

Rubyの「ブロック変数以外の変数によるシャドウイング」

ブロック変数以外のシャドウイングの例を探してみたところ、ありました。

Rubyスタイルガイドを読む: 文法(5)ブロック、procの「2-37」でも、ローカル変数でメソッドをシャドウイングすることは避けるよう指示があり、Rubocopでもチェックされます。このコード例は、ブロック変数と無関係な、引数名(つまりローカル変数)によるシャドウイングです。

class Foo
  attr_accessor :options

  # ok
  def initialize(options)
    self.options = options
    # ここではoptions と self.optionsが同等になっている
  end

  # 不可
  def do_something(options = {})         # このoptionsの名前を変えるべき
    unless options[:when] == :later
      output(self.options[:message])     # そうすればこのselfは不要になる
    end
  end

  # 良好
  def do_something(params = {})
    unless params[:when] == :later
      output(options[:message])
    end
  end
end

この場合、self.optionsとすることでアクセスできるので、一般的な意味でのシャドウイングに沿うものだと思います。

おまけ

つい最近、-wオプションでブロック変数のシャドウイングの警告が削除されたそうです。

以下のようにあえてブロック変数の名前を変えずにシャドウイングさせて書きたい(名前を変えたくない)場合などが想定されているようです。シャドウイングについてはRubyCopでチェックできるという考え方です。

user = users.find { |user| user.parent == parent }

シャドウイングの警告がRuby 1.9から導入されたことをこの#12490で知りました。

私がGo言語に染まっているせいか、ブロック変数を1文字のuにしたい気持ちがうっすらと…

関連記事

Rubyスタイルガイドを読む: 文法(5)ブロック、proc

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ