Rubyのnilはおともだち

Rubynilは便利ですよというお話です。
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を返すため、メソッドhogenilを返します。
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が返ってしまいます。

まとめ

nilの魅力、少しでも伝わったでしょうか?Rubynilはいつでも側にいる心強いパートナーです。ぜひ味方につけてみましょう。

19日目はtamootさんです。

*1:可読性的にはどうなんでしょう?