【Ruby】パーフェクトRuby 学習感想文 ~余談:インスタンス変数について
師匠(Rubyの分かる先輩)からメールが来ました。
「インスタンス変数が分かってない?」
全然文脈が分かりませんけど、いつもこういう感じなので。
インスタンス変数について言及したのはこの記事くらい?
これを読んで「こいつは分かってない」とばれたのかな。
インスタンス変数について分かっていない自覚はあるので
ここらへんで仕切りなおし。
p52 インスタンス変数
本にはこうある↓
以下に「@length」というインスタンス変数を持つRulerクラスを定義しました。
class Ruler def length=(val) @length = val end def length @length end end ruler = Ruler.new ruler.length = 30 p ruler.length # =>30
ここ読んでたとき、実は気になっていたんだけど
変数宣言って特にしてないよね。
実行している部分だけをみると、「変数ぽいな」とおもうけど、
実際は、代入も取得も、単にメソッドを呼んでいるだけだ。
わざとらしくメソッド名を変えてみると良く分かる↓
class Ruler def set_length (val) @length = val end def get_length @length end end ruler = Ruler.new ruler.set_length 30 p ruler.get_length # =>30
特に @length 自体を直接 見にいってるのではない。
インスタンス変数を直接呼び出せる?
単なるメソッドを呼び出しているに過ぎないから、変数宣言とかそもそもしない、
ってことか。
インスタンス変数を直接呼び出すような方法はないのかな?
えーっと、Javaだったら
class Hoge { public String name; } class Foo { Hoge hoge = new Hoge(); hoge.name = "tarou"; }
みたいに書ける。
こんな感じで@lengthを直接呼んでみよう。
class Ruler name = "jiro" end ruler = Ruler.new p ruler.name
⇒エラー。undefined method `name' for #
class Ruler @length = 44 end ruler = Ruler.new p ruler.@length
⇒エラー。 syntax error, unexpected tIVAR, expecting '('
やっぱりだめか。
Rubyって <レシーバ>.<メソッド名>の大原則があるんだな。
Javaだと、クラスが持つメソッドも変数も同じように扱えたけれど、
Rubyでは、メソッドと変数は厳密に区別されている、ように見える。
本にもこう書いてある(10章 P303)
オブジェクトが持つインスタンス変数の値を参照するには、 そのインスタンス変数に対応するゲッターメソッドが定義されている必要があります。
なぜ頭に「@」?
そもそも何で「@」をつけたんだろう。
「@」が付いてないとどうなるんだろう。
class Ruler def set_length (val) length = val end def get_length length end end ruler = Ruler.new ruler.set_length 30 p ruler.get_length
⇒エラー。`get_length': undefined local variable or method `length' for #
なるほど。@をつけないとスコープがそのメソッド内に限られてしまうのか。
リフレクションでインスタンス変数を確認する
@をつけてインスタンス変数と認識されていることは分かった。
リフレクションで実際に確認してみよう
class Ruler def set_length (val) @length = val end def get_length @length end end ruler = Ruler.new p ruler.instance_variables ⇒[]
え!?インスタンス変数がない!?
あ、ひょっとして。。。
class Ruler def set_length (val) @length = val end def get_length @length end end ruler = Ruler.new p ruler.instance_variables ruler.set_length 30 p ruler.instance_variables
■実行結果
[] [:@length]
なるほどー。
変数宣言しなかったら、利用したタイミングでインスタンス変数として認識されるのか。
自分が10章のブログに書いた間違いって?
10章のブログを改めて読み返し。
class MailSender attr_reader :from def initialize(from) @from = from end end ms = MailSender.new('tanaka') p ms.__send__ :from p ms.instance_variable_get :@from
■実行結果
"tanaka" "tanaka"
自分はこのコードを見て↓
ms.__send__ :from
:fromメソッドなんて定義されてないのに何で実行できるのー、って思ってた。
attr_readerで定義することで、自動的に:fromメソッドが定義されていたのかな?
調べてみよう。
class MailSender attr_reader :from def initialize(from) @from = from end end ms = MailSender.new('tanaka') p ms.methods
■実行結果
[:from, :nil?, :===, ・・・
あー。ばっちりいる。そういうことか。
attr_readerで定義することで、自動的に:fromメソッドが定義されていたのか。
ならattr_accessor なら「:from=」メソッドも定義されるのか。
やってみよう。
class MailSender attr_reader :from attr_accessor :hoge def initialize(from, hoge) @from = from @hoge = hoge end end ms = MailSender.new('tanaka', 'gohan') p ms.methods
■実行結果
[:from, :hoge, :hoge=, :nil?, ・・・]
なるほどー。
自分は10章を読んだとき「インスタンス変数に直接アクセスしてる」と思っていたけど、
そうではなくて、自動的に作成されていたgetterメソッドを呼び出していたんだ!