【一人 bugs.ruby Advent Calendar 2020】[Bug #17030] Enumerable#grep{_v} should be optimized for Regexp【4日目】

一人 bugs.ruby Advent Calendar 2020 4日目の記事になります。

[Bug #17030] Enumerable#grep{_v} should be optimized for Regexp

ary.select { |e| e.match?(reg) } と比較して ary.grep(reg) の方が遅いので最適化しよう、という内容のチケットになります。 これは grep で比較する際に #=== を使って比較しているのが原因です。 Regexp#=== で比較した場合に『最後に比較を行った正規表現マッチの情報』として MatchData を生成しています。

/\d+/ === "homu1234"

# 最後に比較した正規表現を MatchData として返す
matchdata = Regexp.last_match

pp matchdata
# => #<MatchData "1234">

# 比較した文字列を返す
pp matchdata.string
# => "homu1234"

# 比較した正規表現を返す
pp matchdata.regexp
# => /\d+/

# 実際にマッチした文字列
pp matchdata.to_s
# => "1234"

今回の問題のボトルネックはこの MatchData を生成している箇所で /\d+/.match? "homu1234" だとこの MatchData は生成されないのでその分早くなっています。

/\d+/.match? "homu1234"

matchdata = Regexp.last_match
# => nil


ary = ["homu", "mami", "mado"]

# こっちは MatchData は生成されない
ary.select { |e| e.match?(/.*/) }
p Regexp.last_match   # => nil

# こっちは MatchData が生成される
ary.grep(/.*/)
p Regexp.last_match   # => #<MatchData "mado">

このチケットではこの『 MatchData を生成するべきかどうか』の点を主に議論されています。 単純に #grep#===MatchData を生成しないようにすると非互換な変更になってしまいなかなかに難儀している模様。 そこで提案されたのが /hoge/f みたいに正規表現リテラルに f オプションを追加し、それで制御できるようにすればいいのでは?という提案です。 このオプションを利用して

  • /hoge/ => Regexp#=== で判定する
  • /hoge/f => Regexp#match? で判定する

のように用途によって使い分けるという感じです。 こんな感じで正規表現リテラルで制御することによって #grep だけではなくて #any?#all? などでも流用することができます。 正規表現リテラル側で制御する発想はなかったのでこの対応はなかなかかしこいなあ、と思いました。 現時点だとこの議論はストップしていますが気になる方がいれば何かしらコメントしてみるといいと思います。