タイトルの通りです。
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 であろうと引数が評価されることを期待していると、そのようには動かないので注意が必要です。