Ruby 2.5: Enumerableの新機能: トリプルイコール`===`と述語メソッドの合わせ技(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

画像はすべて元記事からの引用です。

Ruby 2.5: Enumerableの新機能: トリプルイコール===と述語メソッドの合わせ技(翻訳)

以下の素晴らしいドキュメントもご覧ください。本記事で書かれていることについて皆さまが気づかなかったことも含めてひととおり載っていて有用です。

トリプルイコール演算子の黒魔術

トリプルイコール演算子に関する以下の私の過去記事を未見の方は、ぜひお読みください。

Ruby inside: トリプルイコール === の黒魔術(翻訳)

今回の新機能についてここから先のいくつかのパラグラフでやんちゃしています。読んだ方はきっといろいろやんちゃなアイデアで心がいっぱいになることでしょう。

述語メソッド

何はともあれ、私がここで言う「述語(predicate)」が何を指すかおわかりでしょうか?ここでは、Enumerableに含まれる述語スタイルのメソッド、すなわち末尾が?のメソッドを指します。

[1,2,3].all? { |n| n.even? }  # => false
[2,4,6].all? { |n| n.even? }  # => true

[1,2,3].any? { |n| n.even? }  # => true
[1,3,5].any? { |n| n.even? }  # => false

[1,2,3].none? { |n| n.even? } # => false
[1,3,5].none? { |n| n.even? } # => true

これらのメソッドのショートハンドについては後述しますが、ひとまずコード例はこれを元にします。

Enumerablegrep

Enumerableにあるのに忘れられがちなメソッドのひとつに、grepがあります。

[1,2,3, 'string'].grep(Numeric) # => [1, 2, 3]

grepのドキュメントで動作を調べてみましょう。

Pattern === elementとなるすべての要素を列挙する配列を1つ返す。

トリプルイコール演算子にこんな便利な使いみちがあることはとっくにご存知かと思いますが、ひとりの勇敢なRubyistがここに少しばかり手を加える決心を固めました。

トリプルイコールに述語メソッドを食べさせる

いよいよ本題です。

上述の述語系メソッドたちが「今こそ、たったひとつの引数を取って先のgrepのように===に適切に応答する時だ」と決意すると、何が起きると思いますか?

マジックです。マジックが花開くのです。

%w(foo bar baz).none?(/foo/) # => false

[1,2,3].all?(Numeric) # => true

これはこれで面白いですが、マジックとしては小粒です。ではもう少しやってみましょうか。

%w(10.0.0.1 10.0.0.5).all?(IPAddr.new('10.0.0.0/8'))

システム管理者なら泣いて喜ぶところです。しかしこれだけではありません。

楽しい楽しい黒魔術

独自の===を定義する方法はもうご存知ですね?いよいよウサギの穴に飛び込むときが来ました。

ArrayHashに機能を追加するラッパーメソッドをこしらえてみたらもっと面白くなると思いませんか?それはもう指折りの楽しさです。

ここで一言: Rubyではこの書き方はよくありません

前述の「トリプルイコール===の黒魔術」記事からコード例を再録します。

people = [
  {name: 'Bob', age: 20},
  {name: 'Sue', age: 30},
  {name: 'Jack', age: 10},
  {name: 'Jill', age: 4},
  {name: 'Jane', age: 5}
]

このデータで遊ぶには、ラッパーをこしらえておく必要があります。

class Q
  def initialize(conds = {}) @conds = conds end

  def ===(other)
    @conds.all? { |k, matcher| matcher === other[k] }
  end
end

def Q(conds = {}) Q.new(conds) end

それではやってみましょう。

people.any?(Q(age: 1..10)) # => true

できました!もっとも技術的にはto_procでも同じことはできますが。

class Q
  def initialize(conds = {}) @conds = conds end

  def ===(other)
    @conds.all? { |k, matcher| matcher === other[k] }
  end

  def to_proc
    -> other { self === other }
  end
end

def Q(conds = {}) Q.new(conds) end

people.select(&Q(age: 20))

これも面白いですね。少なくとも私は面白いと思いました。

もういくつ寝ると使えるの?

いつになるかはまだはっきりしませんが、Matzがこれに祝福を与えました。

了解、ご提案どおりオプション1に決めました。
今後は以下のように書けます。

[1, 3.14, 2ri].all?(Numeric) # => true

if should_be_all_symbols.any?(String)
  ...
end
some_strings.none?(/aeiou/i)

Matz.
11286#note-16より

断言はできませんが、運がよければ今度のクリスマスの2.5でお目にかかれると思います(訳注: ご存知のとおりRuby 2.5で導入済みです)。いずれにしろ待ち遠しくてなりません。

キヅネザルはお好き?

ところで先ほどから本記事でキツネザルくんがちらちら姿を見せていることにお気づきでしょうか?この子たちは今私が執筆中の本からやってきたお客さんです。

baweaver.gitbooks.ioより

最後に

皆さん同様、私も新機能というものが大好きです。Rubyのバグトラッカーを追って書いた記事につき、読みにくさについてはご容赦ください。コアチームが素晴らしい仕事をしてくれたおかげで、私たちは一足早くクリスマス気分を迎えました。

関連記事

Ruby: ループには一時変数ではなくEnumerableを使おう(翻訳)

Rubyの===演算子についてまとめてみた

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

BPSアドベントカレンダー

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ