😎

Ruby のパターンマッチで「〜でない場合は〜」のパターンを書くには…?

2024/08/30に公開

例えば 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 みたいにかけたりすると色々と捗りそうな気がしますが。

GitHubで編集を提案

Discussion