Tech Racho エンジニアの「?」を「!」に。
  • 開発

【続き】Ruby 2.4.1のOnigmo非包含演算子をあえて単独で`#match?`で使ってみた

こんにちは、hachi8833です。先週のTechRacho記事「Ruby 2.4.1新機能: Onigmo正規表現の非包含演算子(?~ )をチェック」の続編です。

ここで行っている非包含演算子の単独利用は挙動を理解するための極端なケースであり、実用的な意味はありませんのでご了承ください。

非包含演算子の挙動

Ruby 2.4.1新機能: Onigmo正規表現の非包含演算子(?~ )をチェック」記事を作成中に以下のサンプルコードを見ていて、ふと気になった点がありました。

"うらにはにわにわにはにわにわとりがいる".match?(/(?~でも)/) #=> true
"うらにはにわにわにはにわにわとりがいる".match?(/(?~には)/) #=> true
  • 空文字列""は「指定の文字列を含まない部分文字列」に該当するのだろうか?
  • 集合論的には該当する気がする
    • 空集合も集合だから
  • ということは、空文字を含むどんな文字列に対しても空文字を見いだせてしまうことになる
    • なぜなら文字と文字の間は空文字であり、どんな文字列でも文字と文字の間にもれなく空文字があることになるから

そうすると非包含演算子(?~)のみを使った#match?どんな文字列に対してもtrueになってしまうのだろうか?

やってみようみてみよう

そうなりました。空文字列に対する#match?でもtrueになりました。

"".match?(/(?~でも)/)              #=> true
"This is a pen".match?(/(?~でも)/) #=> true

すべてを試したわけではありませんが、#match?の中で非包含演算子だけを使ったが最後、対象の文字列の内容にかかわらずこのようにtrueが返ると考えられます。

そしてさらに、非包含演算子(?~)の中を空っぽにすると、#match?は空文字を含めて、文字列の種類を問わずfalseになります。

"".match?(/(?~)/)              #=> false
"This is a pen".match?(/(?~)/) #=> false

最初この挙動を見たときにバグかと思ってしまいましたが、上述のように空文字列""が部分文字列に該当するのであれば、この動作は筋が通っています。

中の人に聞いてみた

以上ざっくり仮説を立てたうえで、記事公開後の夜にOnigmoのIssue #86で聞いてみました。

その結果、上の非包含演算子の挙動はやはり正常であるとのことでした。以下に大意をメモします。

この挙動は正常です。

非包含演算子(?~somestring) は ""や"s"や"so"や"som"..."somestrin"にもマッチしますし、"somestring"を含まないあらゆる文字列にもマッチします。どんな文字列にも "" が含まれるので、あらゆる文字列とマッチします。
逆に空の非包含演算子(?~)は、どんな文字列にも "" が含まれるので、どんな文字列とも決してマッチしません。

非包含演算子を単独で使うのはあまり使いやすくないかもしれません。アンカーや前後の文字列(prefixやsuffix)と組み合わせて使うのがよいでしょう。

ご回答ありがとうございました ?
おかげさまで非包含演算子の挙動を飲み込めました。「空文字列""は、空文字列を含むどんな文字列にも部分文字列として含まれる」のがポイントだったんですね。

まとめ

  • 非包含演算子のみを含む正規表現/(?~regex)/は、対象の文字列にかかわらず#match?常にtrueになる
  • 空の非包含演算子のみを含む正規表現/(?~)/は、対象の文字列にかかわらず#match?常にfalseになる
  • 以下のように他の文字列などを非包含演算子の前後に置くのが実用的
    • /うらにわ(?~にわ)にわには/

関連記事


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。