初心者が混乱しやすいRubyの変数

はじめに

Rubyの初心者にありがちなことだと思うが、 変数を代入した時や関数を呼び出した時に、値渡しであるか参照渡しであるか混乱すると思う。

私もよくわからずにかつては混乱した。

次のページを見つけてようやく頭の整理がついた。

Rubyのメソッドの引数は値渡しで、Rubyの変数は全てオブジェクトを指し示すポインタに似た何かだと考えればよいのだ。 引数の参照渡し(変数渡し)なのか単なるオブジェクトへの参照なのか、それが問題

なるほどわかりやすい。

  • Rubyのメソッドの引数は値渡し
  • Rubyの変数は全てオブジェクトを指し示すポインタに似た何か

Rubyの変数はラベル

Cにおける変数は箱だと例えられるが、Rubyにおける変数はラベルと例えられることがある。

Cの変数では、メモリ上の箱に値がそのまま入っている。代入をすれば、箱が複製される。

一方、Rubyではメモリ上の箱のどこかに値が入っており、変数はラベル(箱への参照)を持っているだけにすぎない。 変数を代入すると、メモリ上のデータはそのままに、ラベルだけがコピーされるのである。

破壊的操作で意図しない変更が起きる例

次の例のようにbにaを代入した後、aに破壊的変更をすると、bの値まで変わってしまう。

なぜならば、aとbは同じラベルを持っているので、同じ箱を共有した状態にあるからだ。

# 破壊的操作でbに意図しない変更が起こる例
[1] pry(main)> a = "abc"
=> "abc"
[2] pry(main)> b = a
=> "abc"
[3] pry(main)> a.reverse!
=> "cba"
[4] pry(main)> b
=> "cba"

回避方法1

bの値を変えたくなければ、破壊的操作をしなければ良い。

a = a.reverseとすれば、インスタンス"cba"が作られ、aにそのラベルが入る。

# 破壊的操作をしなければbは変わらない
[1] pry(main)> a = "abc"
=> "abc"
[2] pry(main)> b = a
=> "abc"
[3] pry(main)> a = a.reverse
=> "cba"
[4] pry(main)> b
=> "abc"

回避方法2

または、b = aではなく、b = a.dupとすれば、別のインスタンス"abc"が作られ、bにそのラベルが入る。

a[1] = "b"のように、どうしても破壊的な変更をしないといけないときには必要だ。

# dupを使って深いコピーをする
[1] pry(main)> a = "abc"
=> "abc"
[2] pry(main)> b = a.dup
=> "abc"
[3] pry(main)> a.reverse!
=> "cba"
[4] pry(main)> b
=> "abc"

Rubyのメソッドの引数は値渡し

Rubyのメソッドの引数は値渡しだと言ったが、渡される値はラベルである。

だから、メソッドの中で破壊的操作をすれば、意図しない変更が起こる。

# メソッドでも破壊的操作で意図しない変更が起きる例
[1] pry(main)> def hoge(p)
[1] pry(main)*   p.reverse!
[1] pry(main)* end  
=> nil
[2] pry(main)> a = "abc"
=> "abc"
[3] pry(main)> hoge(a)
=> "cba"
[4] pry(main)> a
=> "cba"
[5] pry(main)>

回避方法は先程と同じで、破壊的操作をしなければ良いだけである。

補足

補足だが、関数の中で仮引数に別の値を代入しても、実引数には影響がない。

なぜならば、仮引数pに”hoge”を代入しても、 これまで説明したルール通り、pの参照先が上書きされただけなので、 実引数aの指す値には影響がないからである。

#  仮引数に再代入しても、実引き数には影響がない例
[1] pry(main)> def hoge(p)
[1] pry(main)*   p = "hoge"
[1] pry(main)* end  
=> nil
[2] pry(main)> a = "abc"
=> "abc"
[3] pry(main)> hoge(a)
=> "hoge"
[4] pry(main)> a #影響がない
=> "abc"

おまけ:Rubyにインクリメント演算子がない理由

Rubyにインクリメント演算子がない理由は簡単で、RubyのFixnumクラスやBignumクラスは immutableだからだ。

つまり、オブジェクト自体を破壊的に変更することはできない。

もし、オブジェクト自体を破壊的に変更できたらどうなるだろうか。

大変なことになってしまう。

# インクリメント演算子のあるRuby
[1] pry(main)> a = 1
=> 1
[2] pry(main)> b = a
=> 1
[3] pry(main)> a++
=> 2
[4] pry(main)> a
=> 2
[5] pry(main) b
=> 2
[6] pry(main) 1
=> 2

1 = 2が成立してしまう。

もちろん、実際のRubyではエラーになってしまうので、こんなことはありえない。

インクリメント演算子はC言語などのように、変数に値が入っている言語なら問題ない。

しかし、Rubyのように数値までオブジェクトとなっている言語では、 インスタンスの値を変更できてしまうと、大変なことになってしまう。

だから、immutable にするしかない。

ちなみに、Rubyではi += 1のような演算子がある。

これはどうみても破壊的だしどうなっているんだと思ったが、シンタックスシュガーらしい。

つまり、こういうことだ。

i += 1 # i = i + 1 と解釈される

どうせなら、i++もシンタックスシュガーにしてしまえばいいじゃないかと思うのは私だけだろうか。

comments powered by Disqus

gam0022.net's Tag Cloud