タイトルの通りです。
Safe Navigation Operator に関するコミット を追いかけていると、ドキュメント中に興味深い一文が見つかりました。
You may use &. to designate a receiver, then my_method is not invoked and the result is nil when the receiver is nil. In that case, the arguments of my_method are not evaluated.
これは例えば obj&.my_method(arg)
というメソッド呼び出しを行った時、 obj
が nil
だったら my_method
への引数である arg
は評価されない、ということです。
試してみる
早速試してみました。
オブジェクトに対して正しくメソッドを呼び出せたとき
class Foo
def my_method(s)
"Foo#my_method is called with \"#{s}\""
end
end
def arg
puts "arg is evaluated"
"m"
end
obj = Foo.new
puts obj&.my_method(arg)
$ ruby safe1.rb
arg is evaluated
Foo#my_method is called with "m"
obj.my_method
の結果が出力される前に、arg
が評価されていることがわかります。
何の違和感もありません。
レシーバのオブジェクトにメソッドが存在しなかったとき
class Foo
end
def arg
puts "arg is evaluated"
"m"
end
obj = Foo.new
puts obj&.my_method(arg)
$ ruby safe2.rb
arg is evaluated
safe.rb:10:in `<main>': undefined method `my_method' for #<Foo:0x007fe859807020> (NoMethodError)
Did you mean? method
NoMethodError
が発生する前に arg
が評価されていることがわかります。
これも特に違和感のない挙動です。
レシーバが nil
だったとき
そしていよいよドキュメントに記載されているケースです。
def arg
puts "arg is evaluated"
"m"
end
obj = nil
puts obj&.my_method(arg)
$ ruby safe3.rb
puts
に nil
が渡されることで空行を出力しますが、arg
は評価されていないことがわかります。
どういうときにうれしいか
これはやはり引数の評価に時間がかかるような場合、不要な評価を行わない分だけ実行時間を節約できます。
foo&.bar(some_heavy_computation_result)
この some_heavy_computation_result
は何か重い処理を行うメソッドだったとして、foo
が nil
なら不要であるところのその呼び出しを省略してくれます。
ActiveSupport
の Object#try!
に比べて高速、という記事も書きましたが、これは通常のライブラリでは実現できない最適化です。
逆に言うとレシーバが nil
であろうと引数が評価されることを期待していると、そのようには動かないので注意が必要です。