OneDay

40歳からプログラマとして生活しています。

Ruby歴半年くらいの自分へ HelloWorld (前編)

#sample1
class HelloWorld
	attr_accessor :name   # 外からアクセス出来る?くらいの認識だった
	def initialize(name = "Alice")
		@name = name  #インスタンス変数に代入しているのは分かる
	end

	def hello
		puts "Hello, #{name}" #なんで@nameじゃないの?
	end
end

alice=HelloWorld.new
alice.hello #=> "Hello, Alice" 

Rubyを始めて半年くらい経ったとき、どうしてもこのコードが理解できずにいたが、久しぶりにたのしいRubyを開いてみて、今なら当時の自分にちゃんと説明できそうな気がする。

当時の疑問

helloメソッドで#{name}とあるがなんで@nameじゃいけないの?


回答→@nameでも良い。helloメソッドの#{}の中身を@nameにして実行しても"Hello, John"は返る。理解を深めるためにattr_accessorを無効にしてみよう。下の2つのコードにおいて、sample2はhelloメソッドに#{@name}を、sample3はhelloメソッドに#{name}を設定してみる。結果はsample2だけ"Hello John"が返る。

#sample2
class HelloWorld
	#attr_accessor :name
	def initialize(name = "John")
		@name = name
	end

	def hello
		puts "Hello, #{@name}"
	end
end

a = HelloWorld.new
a.hello # =>Hello, Johnが返る
#sample3
class HelloWorld
	#attr_accessor :name
	def initialize(name = "John")
		@name = name
	end

	def hello
		puts "Hello, #{name}"
	end
end

a = HelloWorld.new
a.hello # =>NameError, undefined local variable or method `name'

解説:sample2は、helloメソッドに#{@name}を設定しているが、@nameとはインスタンス変数で同じレベル(ここではHelloWorldクラス)で値を持ち運ぶことが出来る。initializeメソッドの@nameの値は、helloメソッドでも使えるということだ。
一方sample3を見てみると、helloメソッドでnameが定義されていないとというエラーとなる。nameといれただけではnameって何?とrubyは理解出来ない。なぜエラーになったのか、local variableとmethodを用いてエラーを解消するコードを書いてみる。

#sample4
class HelloWorld
	#attr_accessor :name
	def initialize(name = "John")
		@name = name
	end

	def hello
		name = @name
		puts "Hello, #{name}"
	end
end

a = HelloWorld.new
a.hello # =>Hello, Johnが返る

解説: sample4ではhelloメソッド内にnameという変数を設定した。そこに@nameを代入している。これでinitializeメソッドで設定した@nameをhelloメソッドに引き渡せたわけだ。

#sample5
class HelloWorld
	#attr_accessor :name
	def initialize(name = "John")
		@name = name
	end

	def name
		@name
	end

	def hello
		puts "Hello, #{name}"
	end
end

a = HelloWorld.new
a.hello # =>Hello, Johnが返る

解説:sample5ではnameメソッドというものを追加した。initializeメソッドの@nameをnameメソッドに引き渡せすことができる。helloメソッドから#{name}でnameメソッドで設定した式が返される。nameメソッドが出たのでアクセサのことに触れる。このnameメソッドの3行ははattr_reader :nameで置き換えることができる。つまりattr_reader:nameはnameメソッドと同じ働きをする。置き換えた後のコードをsample6で確認してみよう。アクセサがattr_readerに変わった以外はsample1とほぼ同じだ。

#sample6
class HelloWorld
	attr_reader :name
	def initialize(name = 'John')
		@name = name
	end

	def hello
		puts "Hello, #{name}"
	end
end

a = HelloWorld.new
a.hello # =>Hello, Johnが返る

当時の私の声が聞こえる。「sample5にあったnameメソッドを用いたほうが分かりやすい気がする」→わかりやすさという点では慣れ。アクセサを使用する大きな利点は使用するインスタンス変数が増えたとき。例えばbirthday, gender, ageなどの属性をこのclassに追加した場合、アクセサだと attr_reader :name, :birthday, :gender, :ageとし、必要に応じてinitializeメソッドインスタンス変数に初期値を入れれば良いが、アクセサを用いなければ、def name, def birthday, def gender, def ageとインスタンスを返すだけのメソッド定義を4つもしなければいけない。


再び当時の私の声が聞こえる。。「そもそもnameメソッドというのがピンと来ない...」
うーん、それではここまでを踏まえて、違うclassで例を挙げてみる。HappyBirthdayクラスで、そのクラスはname, birthday_month, birthday_dateの個人情報を持っていて、本日誕生日の人にメッセージを出力するmessageメソッドを実装することにする。

#sample7
require 'date'
class HappyBirthday
	attr_reader :name, :birthday_month, :birthday_date
	def initialize(name="Alice", birthday_month=1, birthday_date= 1)
		@name = name
		@birthday_month = birthday_month
		@birthday_date = birthday_date
	end

	def message
		if Date.today.month == self.birthday_month && Date.today.day == self.birthday_date
			puts "Happy Birthday! ,#{name}."
		else
			puts "Hello, #{name}"
		end
	end
end

birthday = Date.today
alice = HappyBirthday.new('Alice', (birthday-2).month, (birthday-2).day)
bob = HappyBirthday.new('Bob', (birthday-1).month, (birthday-1).day)
charlie = HappyBirthday.new('Charlie', (birthday-0).month, (birthday-0).day)
dick = HappyBirthday.new('Dick', (birthday-1).month, (birthday-1).day)

[alice, bob, charlie, dick].each do |p|
	p.message
end

# =>Hello, Alice
# =>Hello, Bob
# =>Happy Birthday! ,Charlie.
# =>Hello, Dick が返る

sample7のHappyBirthday クラスで定義しているのはmessageメソッドだけではない。attr_readerによって、nameメソッド、birthday_monthメソッド、birthday_dateメソッドが定義されている。繰り返しになるが、attr_reader :name, :birthday_month, :birthday_dateによって以下がセットされていることに等しい。

def name
  @name
end

def birthday_month
  @birthday_month
end

def birthday_date
  @birthday_date
end

nameメソッド、birthday_monthメソッド、birthday_dateメソッドだが、それぞれのインスタンス変数の値をmessageメソッドに引き渡すだけのメソッドではない。これらのメソッドもオブジェクトに対して機能する。

#sample8
require 'date'
class HappyBirthday
	attr_reader :name, :birthday_month, :birthday_date
	def initialize(name="Alice", birthday_month=1, birthday_date= 1)
		@name = name
		@birthday_month = birthday_month
		@birthday_date = birthday_date
	end
end

birthday = Date.today
alice = HappyBirthday.new('Alice', (birthday-2).month, (birthday-2).day)
bob = HappyBirthday.new('Bob', (birthday-1).month, (birthday-1).day)
charlie = HappyBirthday.new('Charlie', (birthday-0).month, (birthday-0).day)
dick = HappyBirthday.new('Dick', (birthday-1).month, (birthday-1).day)


puts alice.name # => Alice が返る
puts alice.birthday_month # =>11 が返る
puts alice.birthday_date # =>28 が返る

[alice, bob, charlie, dick].each do |p|
	puts "#{p.name}'s birthday is #{p.birthday_month}/#{p.birthday_date}."
end

 
# =>Alice's birthday is 11/28.
# =>Bob's birthday is 11/29.
# =>Charlie's birthday is 11/30.
# =>Dick's birthday is 11/29. が返る

今後Railsを用いてMySQLのdatabaseを取り扱う場合、まさにこのalice.nameといった使い方でaliceの名前を抽出する。
たい焼きの型をクラス、生成されるたい焼きをインスタンスとして説明がある。あえて、インスタンス変数で設定する種のものを表現するとあんこの種類(こしあん、白あん、クリームなど)、価格、砂糖の量、といった要素を表すといえようか。a, b, cというたい焼きを作るとそれぞれを構成する分量がインスタンス変数の中に格納される。{a.anko = 'クリーム', a.price=90, a.sugar=20}, {b.anko ='こしあん', a.price=80, a.sugar=18}みたいな。たい焼きの型からは様々な要素をもつたい焼きがどんどん生成される。
attr_writer,attr_accessor,initializeについては続編で書こう。