チェリー本:パターンマッチ

パターンマッチの基本

rubyのパターンマッチの大きな特徴は下記の3つ。

  1. 配列やハッシュの構造をパターン化して条件分岐させる
  2. in節で=を使わずにローカル変数の宣言と代入が行われる
  3. 独自の変数スコープ(有効範囲)を作らない

上の1.については、パターンマッチは下記のような構文になっている。

casein パターン1
  パターン1にマッチした時の処理
in パターン2
  パターン2にマッチした時の処理
in パターン3
  パターン3にマッチした時の処理
end

見た目はcase文っぽいけど、inの部分が違う。条件分岐という特徴からも、パターンマッチもcase式の一種ということだと思う。 ちなみに本の中では、case/when式で書かれたものをcase文と呼び、case/in式で書かれたものをパターンマッチと呼ぶ。

上の2.については、下記のコードを見るとイメージがつく。

# 配列の場合
case [1, 2, 3]
in [a, b, c]
  puts "#{a} #{b} #{c}"
end
#=> 1 2 3

# ハッシュで値を代入するローカル変数を明示的に指定する場合
case {name: 'Prius', engine: '98ps', motor: '72ps'}
in  {name: vehicle_name, engine: vehicle_engine, motor: vehicle_motor}
  puts "#{vehicle_name} #{vehicle_engine} #{vehicle_motor}"
end
#=> Prius 98ps 72ps

# ハッシュで値を省略する場合
case {name: 'Prius', engine: '98ps', motor: '72ps'}
in  {name: , engine: , motor: }
  puts "#{name} #{engine} #{motor}"
end
#=> Prius 98ps 72ps

パターンマッチの利用パターン

パターンマッチを利用するパターンは下記の7パターン。

valueパターン

valueパターンの特徴は、in節に数値や文字列を直接指定できるという点。case節の式とin節の値が等しければ実行される。

country = 'italy'

case country
in 'japan'
  'こんにちは'
in 'italy' then 'Ciao' # thenを使えば一行で書ける
end
#=> "Ciao"

message = # case文と同様に戻り値を返すので変数への代入もできる。
  case country
  in 'japan'
    'こんにちは'
  in 'italy'
    'Ciao'
  end
message #=> "Ciao"

variableパターン

variableパターンの特徴は、in節のパターンに変数を書いて、ローカル変数の宣言と代入を同時に行うという点。『パターンマッチの基本』の2.の内容にあたる。事前に定義した変数などをin節で使用できる。その際は、^を使う。

alise, name = 'Alise', 'Alise'

case name
in ^alise # in 'Alise'と書いたのと同じ
  'Hey, Alise'
end

arrayパターン

arrayパターンの特徴は、in節に[]を使って配列の構造パターンを指定するという点。『パターンマッチの基本』の2.の配列の内容にあたる。入れ子関係になっていてもOK。

case [1, [2, 3]]
in [a, b]
  "a=#{a}, b=#{b}"
end
#=> "a=1, b=[2, 3]"

case [1, 2, 3]
in [a, *rest] # *を使って、任意の長さを指定できる。
  "a=#{a}, rest=#{rest}"
end
#=> "a=1, b=[2, 3]"

hashパターン

hashパターンの特徴は、in節に{}を使ってハッシュの構造パターンを指定するという点。値に変数を指定すると、その変数に対応する値が格納される。キーの順番はパターンマッチに影響しない。

hashパターンは、ハッシュの各要素がin節で指定したパターンに部分位置していればマッチしたとみなす。よってinの順番に気をつけないと、永久に実施されないメソッドができてしまう。ただ、in節に{}を書いた場合は、『空のハッシュに完全一致』がマッチの条件になる。

基本的には、『パターンマッチの基本』の2.のhashの内容にあたる。 **をつけることで、残りカスをまとめて変数に入れられる。

case{name: 'Alise', age: 20, gender: :female}
in  {name: 'Alise', **rest}
  "rest=#{rest}"
end
#=> "rest={:age=>20, :gender=>:female}"

asパターン

asパターンの特徴は、パターンマッチでマッチしたオブジェクトを変数に代入するという点。=> 変数名と書いてあげると、マッチしたオブジェクトを代入できる。

case {name: 'Alise', age: 20, gender: :female}
in {name: String => name, age: 18.. => age}
  "name=#{name}, age=#{age}"
end
#=> "name=Alise, age=20"

alternativeパターン

alternativeパターンの特徴は、2つ以上のパターンを指定し、どれか1つにマッチすればマッチしたとみなす点。パターンを|で連結する。

case 2
in 0 | 1 | 2
  'matched'
end
#=> "matched"

# _を使うことで、オブジェクトの構造をマッチさせられる。変数_への参照はしない。
case [2021, 4, 1]
in [_,_] | [_, _, _]# 配列の要素が2または、3つならマッチ
 'matched'
end
#=> "matched"

findパターン

findパターンの特徴は、ある範囲を指定して、マッチするものを探すという点。

case[13, 11, 9, 6, 12, 10, 15, 5, 7, 14]
in [*, 10.. => a, 10..=> b, 10..=> c,*]
  "a=#{a} b=#{b} c=#{c}"
end
#=> "a=12 b=10 c=15"