誤りや見落としがありましたら@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)
「隠す」のがシャドウイング。別の方法でアクセスできるかどうかは関係ない。コトバンクの記述に引っ張られすぎ。
Link: Ruby: ブロック変数の「シャドウイング」はシャドウイングなのかが気になって調べた(社内勉強会): https://t.co/0IBr4sptMa
— Yukihiro Matsumoto (@yukihiro_matz) November 3, 2018
直接フィードバックをいただきました。ありがとうございます!やっとすっきりしました🙇。
追記(2018/11/06)
これも初めて知りました😳。
シャドウイングは、Algolあたりで登場した概念だと思いますから、いわゆるオブジェクト指向言語よりもずっと前からありますね。シャドウイングを明確に禁止した言語で私の知る限りもっとも古いものはCLU(1974)なので、これもオブジェクト指向言語ではありませんね。
— Yukihiro Matsumoto (@yukihiro_matz) November 3, 2018
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オプションでブロック変数のシャドウイングの警告が削除されたそうです。
@soutaro shadowing の警告を消しました。ご査収ください。 https://t.co/dOlOiZ2kko
— Yusuke Endoh (@mametter) October 26, 2018
以下のようにあえてブロック変数の名前を変えずにシャドウイングさせて書きたい(名前を変えたくない)場合などが想定されているようです。シャドウイングについてはRubyCopでチェックできるという考え方です。
user = users.find { |user| user.parent == parent }
シャドウイングの警告がRuby 1.9から導入されたことをこの#12490で知りました。
u
にしたい気持ちがうっすらと...