Rubyのパターンマッチ、最高です。
ですが、最近、以下のような使い方をしたいんですができなくて困っています。
ages = [40, 60, 75]
users = [{name: "佐藤", age: 74}, {name: "鈴木", age: 75}]
pp users.any? { |user| user in { name: "鈴木", age: ^ages } }
#=> 鈴木さんが40歳か60歳か75歳ならtrueになってほしい
このコードはRuby3.4.5でfalseを標準出力します。{ name: "鈴木", age: [40, 60, 75] }
の人はいないからです。
こういう複数パターンを論理和っぽく組み合わせたい場合、RubyのパターンマッチングにはAlternativeパターンという方法が提供されています。
users = [{name: "佐藤", age: 74}, {name: "鈴木", age: 75}]
pp users.any? { |user| user in { name: "鈴木", age: 40 | 60 | 75 } }
# => true
これはこれでいいんですが、これだと条件を動的に変更することができません。
実務だと、こういう条件をArrayで管理して分岐で可変にしがちです。
ages = [40, 60, 75]
ages << 70 if some_reason
このArrayをそのままAlternativeパターンに組み込めたら最高なんですが、僕の調べた限り、現状のRubyにはArray(もしくはEnumerable)をそのままAlternativeパターンに組み込むやり方がないです。 調べた限りではAlternativeパターンに対応する言語は多くあるものの、配列をAlternativeパターンにするような構文を持つ言語は無いっぽい。そもそもやりたいことの筋が良くないのかもしれませんね。
ガード節を使ってそれっぽいことはできるんですが、もっとCOOLにしたいんですよね
ages = [40, 60, 75]
users = [{name: "佐藤", age: 74}, {name: "鈴木", age: 75}]
pp users.any? do |user|
case user
in { name: "鈴木", age: age } if ages.include?(age)
true
else
false
end
end
#=> true
ちなみに一行パターンマッチではなぜかうまくガード節に変数渡せなかった..
ages = [40, 60, 75]
users = [{name: "佐藤", age: 74}, {name: "鈴木", age: 75}]
pp users.any? { |user| user in name: "鈴木", age: age if ages.include?(age) }
#=> false if内のageはnilになる
解決策 ===を良い感じにしたオブジェクトに変換する
カスタムクラスを噛まして===
を良い感じにします。
class ArrayAlt
def initialize(array)
@array = array
end
def ===(other)
@array.include?(other)
end
end
class Array
def to_alt
ArrayAlt.new(self)
end
end
ages = [40, 60, 75]
users = [{name: "佐藤", age: 74}, {name: "鈴木", age: 75}]
pp users.any? { |user| user in name: "鈴木", age: ^(ages.to_alt) }
#=> true
だいたいこれで良さそうです。
ただ、Arrayをオープンクラスするのもなんだか嫌じゃないですか。僕はPRにパターンマッチをサラッといれてドヤりたいだけなんです!
こんな感じの構文で良い感じになって欲しいんです。
ages = [40, 60, 75]
users = [{name: "佐藤", age: 74}, {name: "鈴木", age: 75}]
# *^ages で展開されてAlternativeパターンになって欲しい
# なんか他と被らない良い感じのsyntaxはないものか
pp users.any? { |user| user in { name: "鈴木", age: *^ages } }
誰かCOOLな方法を知っている人がいたら教えてください。
2025/07/26追記
神な方々からCOOLな方法を教えてもらったので追記です。
@k_tsjさんには、一行パターンマッチでガード節が使えなったのは後置if扱いになってるからと教えてもらいました。
https://x.com/k_tsj/status/1948668442934186083
@tompngさんには、カスタムクラス噛ませなくてもSetにすれば良いという方法を教えてもらいました。===が引数が集合に属するかどうかの判定になるので、これで良いですね。 https://x.com/tompng/status/1948692641945985321
ages = [40, 60, 75]
users = [{name: "佐藤", age: 74}, {name: "鈴木", age: 75}]
pp users.any? { |user| user in { name: "鈴木", age: ^(ages.to_set) } }
#=> true
これならPRでいきなり投げても大丈夫そう。COOLだ…
お二人共ありがとうございます!