Rubyのnilはおともだち
Rubyのnilは便利ですよというお話です。
Ruby人口を増やしたいなぁということで、とても簡単な話題でいきます。(これでハードルが下がった)
Ruby Advent Calendar jp: 2011 の18日目の記事です。17日目はser1zwさんです。
インスタンス変数
さて、おもむろにirbを開いて、@hogeと打ってみましょう。nilが返ってきます。
@hoge
定義されていないインスタンス変数を参照すると、nilが返ってきます。
では、次のコードをご覧ください。printメソッドの中でインスタンス変数を利用する際に、nil?でnilのチェックをしてから利用しています。
class Message attr_accessor :to, :message def print @to = default_to if @to.nil? @message = '' if @message.nil? puts "To: #{@to}" puts @message end def default_to 'sakairyota' end end m = Message.new m.message = 'hello' m.print
重要なことは、あらかじめインスタンス変数に値がセットされてなくても、printメソッドが使えることです。
printはもっと簡単に書けます。
def print @to ||= default_to puts "To: #{@to}" puts "" puts @message end
if文が ||= に変わりました。これは、
@to = @to || default_to
の意味で、@toがnil(またはfalse)の時だけ、@toにdefault_toを入力するという意味になります。Rubyではデフォルト値の代入でよく使う書き方です。
今さりげなく「またはfalse」と書きましたが、Booleanを扱う時は||=ではなくnil?を使いましょう。
遅延読み込み
class Movie def initialize(filename) @file = load_file(filename) end def use_file # @fileを使った処理 end def load_file(filename) # とても重たい処理 end end
上のコードでは、インスタンスの初期化時に重たい処理を行なっています。しかし、もしかすると、初期化だけして結果的にはファイルを使わないかもしれません。そこで、下のようにします。
class Movie def use_file @file ||= load_file(filename) # @fileを使った処理 end def load_file(filename) # とても重たい処理 end end
最初に use_file を実行するときには @file はnilなので、load_fileが実行されますが、2回目以降は@fileに中身が入っているので、load_fileはパスされます。また、use_fileが実行されなければ、load_fileは一回も実行されません。
小ネタ
残りは小ネタです。
nil.to_i とか、nil.to_sとか、良い感じに取り計らってくれるようです。便利です。(参考... http://doc.okkez.net/static/192/class/NilClass.html )
空のメソッドはnilを返します。
def hoge end p hoge #=> nil
defとかclassとかもnilを返すようです。
Rubyではif文も値を返しますが、
a = if true 'true' else 'false' end p a #=> 'true'
条件に引っかからなければnilを返します。
a = if false 'false' end p a #=> 'nil'
if文で値を代入することはあまりないかと思いますが、こんなのはどうでしょうか?
def hoge if false 'hoge' end end
if文全体はnilを返すため、メソッドhogeもnilを返します。
1行で書くとこんな感じです*1。
def hoge(condition, value) value if condition end
これを理解すると、次のコードが間違いだということが分かります。(これでハマったことがあります。)
# 戻り値として、@record を返す def hoge @record @record.save if some_condition # @record.save は @record自身を返す end
some_conditionがfalseの時、@recordではなくnilが返ってしまいます。
*1:可読性的にはどうなんでしょう?