概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: New in Enumerable — Triple Equals Predicates – Brandon Weaver – Medium
- 原文公開日: 2017/12/02
- 著者: Brandon Weaver
画像はすべて元記事からの引用です。
Ruby 2.5: Enumerableの新機能: トリプルイコール===
と述語メソッドの合わせ技(翻訳)
以下の素晴らしいドキュメントもご覧ください。本記事で書かれていることについて皆さまが気づかなかったことも含めてひととおり載っていて有用です。
トリプルイコール演算子の黒魔術
トリプルイコール演算子に関する以下の私の過去記事を未見の方は、ぜひお読みください。
今回の新機能についてここから先のいくつかのパラグラフでやんちゃしています。読んだ方はきっといろいろやんちゃなアイデアで心がいっぱいになることでしょう。
述語メソッド
何はともあれ、私がここで言う「述語(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
これらのメソッドのショートハンドについては後述しますが、ひとまずコード例はこれを元にします。
Enumerable
のgrep
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'))
システム管理者なら泣いて喜ぶところです。しかしこれだけではありません。
楽しい楽しい黒魔術
独自の===
を定義する方法はもうご存知ですね?いよいよウサギの穴に飛び込むときが来ました。
Array
やHash
に機能を追加するラッパーメソッドをこしらえてみたらもっと面白くなると思いませんか?それはもう指折りの楽しさです。
ここで一言: 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のバグトラッカーを追って書いた記事につき、読みにくさについてはご容赦ください。コアチームが素晴らしい仕事をしてくれたおかげで、私たちは一足早くクリスマス気分を迎えました。