読者です 読者をやめる 読者になる 読者になる

変数を比較するときに NoMethodError が出るのを回避する(Ruby)

変数が定義されている(nil or false でない)とき、その変数の評価をしたい場合がありますよね。例えば

puts a if a > 0

とかです。これは a が定義されていない(nil or false の)場合、当然比較はできないのでエラー(NoMethodError)になります。これを回避したければ、比較の or (あるいは ||)をするとき、or の左側が true ならば右側が評価されないことが使えます*1。ちょっとわかりにくくなってしまいますが、上の例なら、a > 0 を否定し、!a (a の否定)と or にして、if の代わりに unless にします。これで a が比較できるときだけ、a > 0 が評価されます。a が比較されない場合、何もせずに次の行へ移ります。

puts a unless !a or !(a > 0)

かなり技巧的ですが、Ruby には unless があるのでまだマシに書けます。unless がない言語だと、if !(!a or !(a > 0)) みたいに何だかわけのわからない書き方になってしまいます。
 困るのは、a == true の場合なのですよね。これも a は比較できないので、さらに技巧的だが

puts a unless (a == true) or !a or !(a > 0)

とすれば一括して処理できます。

メソッドの返り値で、場合によっては例外で nil や false が返るのはよくあることです。こういう場合に手軽に例外を回避するのに使えるでしょう。けれども、あんまり使うとコードの可読性は減ることになります。

おまけ

or と || の挙動がちがいます。謎である。Ruby 2.2。

irb(main):003:0> a = a or 1
=> 1
irb(main):004:0> a
=> nil
irb(main):005:0> a = a || 1
=> 1
irb(main):006:0> a
=> 1

or の挙動がよくわからない。どうしてこんな仕様になっているのか。

irb(main):001:0> a = a or 1
=> 1
irb(main):002:0> a
=> nil
irb(main):003:0> a = (a or 1) * 2
=> 2
irb(main):004:0> a
=> 2

a ||= 5 と同じことを or でやるには、a = (a or 5).itself とかやるのが思いつくのだけれども、まあ意味がないな。

*1:これを使うので有名な技が、a ||= 1 というやつです。a が定義されていないときだけ 1 を代入します。