みずりゅの自由帳

主に参加したイベントやソフトウェア技術/開発について記録しています

RubyのSafe Navigation Operator(&.)を知る

備忘。
今まで知らなかったので、記憶に留めたいので記録しておく。

hoge&.empty?

のように、「&.」をつけたRubyの記法を、最近知りました。
Safe Navigation Operator」(別名:「ぼっち演算子」)と言って、オブジェクト(この場合「hoge」)がnilでも、例外(NoMethodError)にならずにnilを返してくれるとのこと。


では、実際にそうなるか確認してみます。

まずは、「&.」がない場合。
Hashが空かどうかを判定するために、引数hashに対してempty?を実行するメソッドを用意。
引数に、空Hashもしくは空でないHashを渡せば、メソッド内のempty?は想定通りに動く。
ただし、引数がnilの場合は、nilにempty?メソッドは無いため「NoMethodError」が発生している。

pry(main)> def is_empty(hash)
pry(main)*   hash.empty?
pry(main)* end
=> :is_empty

pry(main)> is_empty({})
=> true

pry(main)> is_empty({"a" => 1})
=> false

pry(main)> is_empty(nil)
NoMethodError: undefined method `empty?' for nil:NilClass


一方、「&.」がある場合。
同じく、Hashが空かどうかを判定するために、引数hashに対してempty?を実行するメソッドを用意。
ただし、内部のロジックでは「&.」を利用して、nilの時でもエラーにならないように意識している。
引数に、空Hashもしくは空でないHashを渡せば、メソッド内のempty?は、やはり想定通りに動く。
では、引数をnilの場合はどうか。
今回は、hash&.の時点でnilが判定されるため、empty?を実行せずにnilが返ってきている。*1
そのため、NoMethodErrorは発生していません。

pry(main)> def is_empty_safe(hash)
pry(main)*   hash&.empty?
pry(main)* end
=> :is_empty_safe

pry(main)> is_empty_safe({})
=> true

pry(main)> is_empty_safe({"a" => 1})
=> false

pry(main)> is_empty_safe(nil)
=> nil


なお、「&.」の挙動としては、「レシーバーがnilでない場合に、その後に続くメソッドを呼び出す」のだそうです。
比較対象にされる「#try」は「(respond_to? メソッドを利用して)メソッドを呼び出し可能な場合にメソッドを呼び出す」とのこと。


ちなみにこの機能、Ruby2.3から実装されているということ。。。

docs.ruby-lang.org

これを知った時、いかに時代に取り残されていたか*2...と感じてしまった。

とはいえ、知らなかったことを知れただけでも、昨日の自分から一歩前進したということで「ヨシ!」とします。

*1:ただ、このメソッドの場合はnilをそのまま返すよりもtrueまたはfalseの扱いにして返すのが良いでしょう。本題とはずれるため、ここではそこまで言及はしませんが

*2:直近の数年では、2.2系を使ったシステムをメンテしていたため...という言い訳。