Pattern matching in depth

Hashes and arrays

case/in matches the shape of a value and binds the parts in one step. A hash pattern names the keys it needs and ignores the rest; an array pattern names the positions and checks the length:

ruby -e 'case {a: 1}; in {a: Integer => n}; puts n; end'

1

ruby -e 'case [1, 2]; in [Integer => a, Integer => b]; p a + b; end'

3

The hash pattern is a subset match: extra keys are allowed. The array pattern matches the exact length unless it contains a splat. A pattern that does not match falls through to the next in or to else, and a case/in with no match and no else raises NoMatchingPatternError, unlike a case/when.

Find patterns

A splat on both sides matches a middle element, which is how a command line or a list is searched without an index:

ruby -e 'case [1, 2, 3]; in [*, 2, *]; puts :found; end'

found

The pattern binds nothing by default; name the parts with *before and *after when the surroundings matter. One splat is allowed per array pattern.

Guards

An if after a pattern adds a condition the shape cannot express. The pattern binds first, then the guard reads the bindings:

case [2, 1] in [a, b] if a > b puts "descending #{a}, #{b}" in [a, b] puts "ascending #{a}, #{b}" end

ruby -e 'case [2, 1]; in [a, b] if a > b; puts "descending #{a}, #{b}"; in [a, b]; puts "ascending #{a}, #{b}"; end'

descending 2, 1

A guard that raises propagates the error; a guard that is false moves to the next pattern. That is the same rule as a when with &&, with the bindings available.

deconstruct and deconstruct_keys

A class becomes matchable by defining one or both methods. deconstruct returns an array for array patterns and deconstruct_keys returns a hash for hash patterns:

class Point def initialize(x, y) @x = x @y = y end def deconstruct = [@x, @y] def deconstruct_keys(_keys) = { x: @x, y: @y } end case Point.new(1, 2) in {x: Integer => x, y: Integer => y} puts x + y end

ruby deconstruct.rb

3 12

The second line in the output is the same object through an array pattern. Struct and Data define both methods already, so a value object is matchable without code.

What to watch for

A hash pattern ignores extra keys; an array pattern does not ignore extra elements. That difference decides which one to use for data that may grow, and it is the first thing to check when a pattern that looks right does not match.

in without a case returns a boolean when used with =>: value in {a: Integer} is the one-line test. It raises NoMatchingPatternError with in and returns false with =>, which is the version for a condition.

Next: Threads and fibers

Article author: Arthur Isaev