Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

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


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。