プログラミングを学び始めて1日目でも書けそうなコードで躓いてしまったのですが、やっぱり基本は大事だなと改めて思ったので、書き留めることにします。
皆さんは以下のコードを実行するとどのような結果になるかわかりますか。
class Human
puts "今から喋ります!"
say
def say
puts "こんにちは"
end
end
結果は以下のようにエラーとなります。
$ ruby human.rb
今から喋ります!
human.rb:4:in `<class:Human>': undefined local variable or method `say' for Human:Class (NameError)
from human.rb:1:in `<main>'
エラーコードを読んでエラー原因がわかった方も多いと思います。
なるほど、メソッドを先に定義していないからエラーになったんだ。と。
では、さっそく以下のように書き直しましょう!
class Human
def say
puts "こんにちは"
end
puts "今から喋ります!"
say
end
以下が結果です。
$ ruby human.rb
今から喋ります!
human.rb:8:in `<class:Human>': undefined local variable or method `say' for Human:Class (NameError)
from human.rb:1:in `<main>'
もうやだ!プログラミングやめる!!
このエラーを理解するためには、クラスから呼び出すメソッドと、インスタンスから呼び出すメソッドは勝手が違うということを理解する必要がありました。
###1.クラスからメソッドを呼び出すときは、メソッドをクラスメソッドに定義する
以下のようにメソッド名に**"self."**を追加してメソッドをクラスメソッドにして実行してみます。
class Human
#before
# def say
# puts "こんにちは"
# end
#
# puts "今から喋ります!"
# say
#end
#after
def self.say
puts "こんにちは"
end
puts "今から喋ります!"
say
end
実行結果:
$ ruby human.rb
今から喋ります!
こんにちは
無事にエラーが消えて、処理が実行されました!
この理由は**self.**をメソッド名の左側に明示的に記述することで、このメソッドのレシーバーにクラスを定義します。
(言い換えればインスタンスメソッドからクラスメソッドになります。)
レシーバーとは、メソッドの戻り値をレシーブ(受け取る)ところを指します。
【修正】レシーバーとは、メソッドの呼び出し元を指します。
class Class
def self.say
p "#{self}がレシーバーです。"
end
say
end
以下の実行結果のように**self.**にあたるレシーバーはクラスオブジェクトです。
$ ruby class.rb
"Classがレシーバーです。"
一方で、普段何気なく書いてる以下のようなメソッドはインスタンスメソッドです。
しかし、どこにもレシーバーを定義する**self.**の記述はありません。
def say
puts "こんにちは"
end
インスタンスメソッドはself.が暗黙的に省略されてるだけで、実際には以下のような形が正確です。
def say
self.puts "こんにちは"
end
では以下のようにインスタンス化して、インスタンスメソッドを呼び出してみます。
class Class
def call
p "#{self}がレシーバーです。"
self.p "#{self}がレシーバーです。"
end
end
class Main
p instance = Class.new
instance.call
end
実行結果を確認すると、selfがインスタンスIDになっていることが確認できました!
$ ruby instance.rb
#<Class:0x0000564fd89e0498>
"#<Class:0x0000564fd89e0498>がレシーバーです。"
"#<Class:0x0000564fd89e0498>がレシーバーです。"
###2.プログラムは原則記述されたコードを上から順番に処理されていく
大前提ではあるのですが、気がついたらこの感覚がなくなってました。
最近はRailsを触ることが多く、プログラムのエントリーポイントとなるようなクラスを触ることがないので、記述する処理はメソッドとして書くことが多く、クラス内の実行順序を意識しなくなっていたようです。
###3.インスタンスメソッドはメソッドを記述したクラス内の各メソッドの記述順序を気にしない。
上記2の話に繋がりますが、今回の学びで一番の気づきは3の部分です。
以下のように改修します。
- 先程のsayメソッドを呼び出す処理をメソッド化(call)
- 別クラスからHumanクラスをインスタンス化
- callメソッドを呼び出す
class Human
# before
# puts "今から喋ります!"
# say
#
# def say
# puts "こんにちは"
# end
# after
def call #1
puts "今から喋ります!"
say
end
def say
puts "こんにちは"
end
end
class Main
human = Human.new #2
human.call #3
end
以下実行結果:
$ ruby human.rb
今から喋ります!
こんにちは
メソッド化する前は、sayメソッドを定義する前に同クラス内でsayメソッドを呼び出すとエラーになりました。
しかし、sayメソッドを定義する前にsayメソッドを呼び出すcallメソッドを定義した場合、別クラスでインスタンス化し、callメソッドを実行してもsayメソッドを呼び出すことが出来ます。
###所感
この問題に行き当たった理由は、エントリーポイントとなるプログラムを書いていた際、よく使うメソッドを一連の実行処理のブロックの下にまとめていたのですが、実行してみるとエラーが発生したためです。
これまでなんとなく手続き型言語は1つのファイルを上から下に処理していくもので、オブジェクト指向言語は指定した変数やメソッドを探しだして実行するイメージを持っていましたが、プログラムというのは上から下に流れてくものだってことを改めて認識させられました。
あまりに初歩的で少し恥ずかしい失敗ではありましたが、大怪我する前に学ぶことが出来てよかったです…!