以下のようなコードを書いていて想定と違う動きだったので調べた。この辺全然理解してなかった。
#!/usr/bin/env ruby
def normalized_name(origin_str)
str = origin_str
str.sub! '-', ''
str.capitalize!
end
snack = 'caramel-corn'
p '元のString snackの値:' + snack
p 'normalized_nameの実行結果:' + normalized_name(snack)
p '元のString snackの値:' + snack
"元のString snackの値:caramel-corn"
"normalized_nameの実行結果:Caramelcorn"
"元のString snackの値:Caramelcorn"
メソッドの中でどんな好き勝手引数の値を変更しても、メソッドに渡す前のトップレベルで定義されているsnackの値は変わらないと思い込んでたけど、そうではない。
Rubyは値渡しだが、初級者がひっかかりやすいのは参照の値渡し(共有渡し)
引数に渡す前の変数がかわってしまうという挙動を見て、参照渡しなのか?と思ってRubyistMagazineで調べたら、
Ruby (や多くのプログラミング言語) は値渡しである。
と明確に書かれているが、
初級者が特にひっかりやすい「参照の値渡し」
とも書かれていて見事にひっかかってました。
■object_idで確認
rubyのオブジェクトに実装されているobject_idメソッドを使うと、オブジェクト固有のidを取得できる。これによって同じオブジェクトを参照しているのか、別のオブジェクトを参照しているのか確認できる。
■参照の値渡しサンプル
# 'test'というStringオブジェクトを作成しstrに代入
irb(main):001:0> str = 'test'
irb(main):002:0> str.object_id
=> 69917655324020
# strの参照先をstr2にコピー(参照の値渡し)
irb(main):003:0> str2 = str
irb(main):004:0> str2.object_id
=> 69917655324020 # さっきと同じobject_id
# どこからも参照されてない'test'という新しいStringオブジェクトを作成
irb(main):005:0> 'test'.object_id
=> 69917655229860 # 新しいobject_id
# さらに'test'という新しいStringオブジェクトを作成し、str2の向き先を変更
irb(main):006:0> str2 = 'test'
irb(main):007:0> str2.object_id
=> 69917655167360 # 新しいobject_id
次のように解釈したら矛盾がなさそうなので、そう理解した。
Rubyは値渡しではあるけども、Rubyにおける値はすべてがオブジェクトなので、実際には変数が持つのはオブジェクトへの参照である(RubyistMagazineの説明におけるメモリアドレス)。変数が値自体を持つことはない。
なので、実質Rubyは参照の値渡しである。
(と解釈しましたがもし誤解や例外などあれば教えてください!!)
代入とreplace
代入は、変数の向き先(参照先)を変更する。=演算子で実行。
replaceは、参照先のオブジェクトが保持する値自体を変更する。rubyの破壊的メソッドはreplaceを使っているらしい。
※ちなみに、FixnumなどのImmutableオブジェクトは破壊的な変更はできない。(replaceメソッドも用意されてない)2014や1986などのFixnum型を何回作成しても同じオブジェクトを参照する。
■replaceのサンプル
# 'test'というStringオブジェクトを作成し、strに代入し、その参照先をstr2にもコピー(上の例と同じ)
irb(main):001:0> str = 'test'
irb(main):002:0> str.object_id
=> 69907653151280
irb(main):003:0> str2 = str
irb(main):004:0> str2.object_id
=> 69907653151280
# str2が参照するオブジェクト自体の値をreplaceによって変更する
irb(main):005:0> str2.replace 'experiment'
=> "experiment"
# str2変数の中身だけでなく、strの中身もかわる
irb(main):006:0> str2
=> "experiment"
irb(main):007:0> str
=> "experiment"
■一番はじめの例のおさらい
実行環境
% ruby -v
ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-linux]