LoginSignup
21
15

More than 5 years have passed since last update.

Safe Navigation Operator で呼ばれるメソッドの引数はレシーバが nil なら評価されない

Last updated at Posted at 2016-02-28

タイトルの通りです。

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) というメソッド呼び出しを行った時、 objnil だったら my_method への引数である arg は評価されない、ということです。

試してみる

早速試してみました。

オブジェクトに対して正しくメソッドを呼び出せたとき

safe1.rb
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 が評価されていることがわかります。
何の違和感もありません。

レシーバのオブジェクトにメソッドが存在しなかったとき

safe2.rb
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 だったとき

そしていよいよドキュメントに記載されているケースです。

safe3.rb
def arg
  puts "arg is evaluated"
  "m"
end

obj = nil
puts obj&.my_method(arg)
$ ruby safe3.rb

putsnil が渡されることで空行を出力しますが、arg は評価されていないことがわかります。

どういうときにうれしいか

これはやはり引数の評価に時間がかかるような場合、不要な評価を行わない分だけ実行時間を節約できます。

foo&.bar(some_heavy_computation_result)

この some_heavy_computation_result は何か重い処理を行うメソッドだったとして、foonil なら不要であるところのその呼び出しを省略してくれます。
ActiveSupportObject#try! に比べて高速、という記事も書きましたが、これは通常のライブラリでは実現できない最適化です。
逆に言うとレシーバが nil であろうと引数が評価されることを期待していると、そのようには動かないので注意が必要です。

21
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
15