Ruby のパターンマッチで「〜でない場合は〜」のパターンを書くには…?
例えば Ruby のパターンマッチで nil じゃない場合〜や String でない場合〜みたいな否定を条件にする場合にパッとみ構文としては存在指定なさそうだったのでどうすればいいか考えてみました。
イメージ以下のような感じです。
def update(params)
case params
in { id: nil じゃない場合, name: }
User.find(id).update!(name:)
end
end
1. ガード節を利用する
パターンマッチでは束縛している値を参照し、ガード節でマッチする条件を制御することができます。
def update(params)
case params
in { id:, name: } if !id.nil?
[id, name]
end
end
# OK
pp update(id: 1, name: "homu")
# => [id, name]
pp update(id: nil, name: "homu")
# => error: NoMatchingPatternError
if ではなくて unless も利用できます。
def update(params)
case params
in { id:, name: } unless id.nil?
[id, name]
end
end
2. Proc オブジェクトを利用する
パターンマッチでは #=== を用いてマッチされるかどうかの判定が行われます。
Proc#=== は Proc#call の alias なので次のように #=== での行うことができます。
is_not_nil = -> { !_1.nil? }
# Proc#=== は #call の alias になっている
pp is_not_nil === nil # => false
pp is_not_nil === 0 # => true
なので Proc オブジェクトを渡すことで『より具体的にどうマッチするのか』を判定することができます。
def update(params)
case params
in { id: -> { !_1.nil? } => id, name: }
[id, name]
end
end
# OK
pp update(id: 1, name: "homu")
# => [id, name]
pp update(id: nil, name: "homu")
# => error: NoMatchingPatternError
NOTE: マッチする値を定義した場合はキーの名前で束縛されないので => id で改めて値を束縛する必要があります。
3. 否定するヘルパメソッドを定義する
現状だとパターンに { id: nil } を書くと内部で nil === id みたいな判定が行われます。
なのでこれを !(nil === id) と判定されるようなヘルパメソッドを定義して対応してみます。
def not_(obj)
proc { !(obj === _1) }
end
def update(params)
case params
in { id: ^(not_ nil) => id, name: }
[id, name]
end
end
# OK
pp update(id: 1, name: "homu")
# => [id, name]
pp update(id: nil, name: "homu")
# => error: NoMatchingPatternError
今回は not_ の評価結果をパターンマッチの値として利用したいので ^ で式を評価するようにする必要があります。
4. Symbol#to_proc を利用する
前提の話からずれちゃうんですが『真の場合にマッチする』のであれば :itself.to_proc で実現することもできます。
def update(params)
case params
in { id: ^(:itself.to_proc) => id, name: }
[id, name]
end
end
# OK
pp update(id: 1, name: "homu")
# => [id, name]
pp update(id: nil, name: "homu")
# => error: NoMatchingPatternError
:itself.to_proc は -> { _1.itself } と同等で『 nil の場合は nil を返す』のでパターンにマッチしないことになります。
まとめ
一番コードとしてスッキリするのは 3. ですかね。
ヘルパメソッドは予め定義しておく必要はありますが。
4. は色々と応用がきく書き方なので何かしら利用できる場面はありそうですね。
理想は本体で !nil みたにかけたり :itself.to_proc を &:itself みたいにかけたりすると色々と捗りそうな気がしますが。
Discussion