せかいや

いまいるここを、おもしろく http://sekai-in-the-box.appspot.com/

【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 # (NoMethodError)

 

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 # (NameError)

なるほど。@をつけないとスコープがそのメソッド内に限られてしまうのか。

 

リフレクションでインスタンス変数を確認する

@をつけてインスタンス変数と認識されていることは分かった。
リフレクションで実際に確認してみよう

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メソッドを呼び出していたんだ!