if var¶
変数がif
の条件式に使われたとき、then
節の中ではその変数はNil
型を持たないと判断されます。
a = some_condition ? nil : 3
# a is Int32 or Nil
if a
# ここに到達するためには if が真でなければならない
# ということは、a が nil というのはあり得ないので、ここでは必ず Int32 である
a.abs
end
if
の条件式中で変数への代入が行われた場合にも、この判断がなされます。
if a = some_expression
# ここでは a は nil ではない
end
条件式でかつ (&&
) が使われた場合にも同様です。
if a && b
# ここでは a も b も Nil ではないことが保証される
end
&&
式の右辺が評価された場合、a
がNil
ではないことも同様に保証されています。
もちろん、then
節の中で変数へ再代入を行なった場合は、その代入された式に応じて変数の型が変わります。
制約¶
これまで書いてきたことはローカル変数に対してのみ機能します。インスタンス変数、クラス変数、クロージャに束縛された変数に関してはこの機能は機能しません。なぜならこれらの変数の値は別のファイバーによってnil
に変更される可能性があるからです。また、定数に対しても機能しません。
if @a
# ここでも `@a` が nil の可能性がある
end
if @@a
# ここでも `@@a` が nil の可能性がある
end
a = nil
closure = ->{ a = "foo" }
if a
# ここでも `a` が nil の可能性がある
end
この制約は値をローカル変数に代入することで回避できます。
if a = @a
# ここで `a` は nil にはならない
end
他の手法として、標準ライブラリのObject#try
メソッドを使うこともできます。これはnil
でない場合にのみブロックを実行する、というメソッドです。
@a.try do |a|
# ここで `a` は nil にはならない
end
メソッド呼び出し¶
Proc やメソッドの呼び出し (ゲッターやプロパティも含む) の場合にも当てはまりません。なぜなら、Nil を許容する (もしくは、複数の型の組み合わせとなるユニオン型の場合がより一般的でしょう) Proc やメソッドの呼び出しの場合、連続した呼び出しであっても、それらが常に同じ型を返すとは限らないからです。
if method # 最初の呼び出しでメソッドは Int32 か Nil を返す
# ここで、最初の呼び出しが Nil を返していないことはわかっている
method # 2度目の呼び出しでも Int32 か Nil を返す
end
こういった Proc やメソッド呼び出しの場合にも、上記でインスタンス変数に関して記載したテクニックが有効です。