こんにちは、hachi8833です。
Rubyの否定演算子「!
」を2つ重ねた「!!
」を使ってメソッドの戻り値をtrue/falseに揃える方法を知ったのでメモします。以下、便宜上「二重感嘆符」と表記します。
対象
- Ruby: 2.3.1
- Rails: 5.1.0.alpha
core_ext/regexp.rbの二重感嘆符「!!
」
Active Supportをたどっていて、Rails 5.1.0.alphaのcore_ext/regexp.rbで以下のコードを見つけたのがきっかけでした。
def match?(string, pos = 0)
!!match(string, pos)
end unless //.respond_to?(:match?)
気になったのは二重の感嘆符「!!
」です。
!!match(string, pos)
で呼び出しているRegexp#matchはRubyのコアメソッドで、マッチしない場合にnil
を返します。
Rubyでは、true
かfalse
のどちらか(論理値)だけを返すメソッド名の末尾にはmatch?
のように疑問符を追加する慣習があります。
この二重感嘆符「!!
」を使うと、nil
を返すmatch
メソッドを簡単にmatch?
のように論理値メソッド化できます。
「!!
」の動作: すべてをtrue
かfalse
にする
ご存じのとおり、Rubyではnil
とfalse
以外のオブジェクトはすべてtrue
として扱われます。そして否定の論理演算子「!
」はtrue
とfalse
を反転します。
!nil
#=>true
!!nil
#=>false
!false
#=>true
!!false
#=>false
!!その他何でも
#=>true
反転の反転は論理上何も変化をもたらしませんが、表現がtrue
かfalse
に揃えられるところがポイントです。
「!!
」を試してみる
Ruby 2.3.1で「!!
」の動作を実際に確認してみます(注: Rails環境ではありません)。
" "
で囲んだ文字列リテラルはさすがにwarningが表示されましたが、試した範囲ではいずれも論理値オブジェクトtrue
かfalse
に変換されました。
Ruby 2.3.1にRegexp#match?
を導入してみる
「!!
」の動作を確認できたので、続けて先ほどの#match?
を試しにRuby 2.3.1に導入してみましょう。
class Regexp
def match?(string, pos = 0)
!!match(string, pos)
end
end
簡単のため、元のコードの末尾にあった
unless //.respond_to?(:match?)
は省略しています。
#match?
が最小限の簡潔なコードで論理値を返すようになりました。if
文を使わずに書けるところがキモチイイですね。
追記: core_ext/regexp.rbの#match
について
core_ext/regexp.rbの#match?
メソッドは、2.4より前のRubyで#match
メソッドとの互換性を取るために、Ruby 2.4の登場に先がけて今年の夏頃masterに追加されたようです。
masterは5.1.0.alphaにつき、今後さらに変更される可能性がありますのでご了承ください。
Defines
Regexp.match?
for Ruby versions prior to 2.4. The predicate has the same interface, but it does not have the performance boost. It's purpose is to be able to write 2.4 compatible code.Xavier Noria
5.1.0.alpha の Active Support: CHANGELOG.mdより
追記: 感嘆符の呼び方について
感嘆符「!
」は日本語だとビックリマークとエクスクラメーションマークと書かれたりしますが、英語圏のプログラミング関連では「bang」と表記されることが多いようです。正式なexclamation markだと長ったらしいからでしょう。
二重感嘆符「!!
」ならdouble-bangやbang-bangなどと書かれることが多いので、ググる場合に参考にしてください。
Rubyでは値を変更する破壊的メソッド名の末尾に「!
」を付ける慣習がありますが、これも英語圏ではbang methodと呼ばれることがよくあります。