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