a way to specify a pattern for our data and if data are matched to the pattern we can deconstruct them according to this pattern
Basic match operator in Elixir
```elixir
iex> x = 4
4
iex> 4 = x
4
```
```ruby
irb> x = 4
=> 4
irb> 4 = x
Traceback (most recent call last):
3: from /home/agnieszka/.rvm/rubies/ruby-2.7.0-preview1/bin/irb:23:in `'
2: from /home/agnieszka/.rvm/rubies/ruby-2.7.0-preview1/bin/irb:23:in `load'
1: from /home/agnieszka/.rvm/rubies/ruby-2.7.0-preview1/lib/ruby/gems/2.7.0/gems/irb-1.0.0/exe/irb:11:in `'
SyntaxError ((irb):2: syntax error, unexpected '=', expecting end-of-input)
4 = x
^
```
Pattern matching in Ruby - basics
```ruby
case expression
in pattern [if|unless condition]
...
in pattern [if|unless condition]
...
else
...
end
```
Array
Pattern matching with Array
```ruby
case [1, 2]
in [2, a]
:no_match
in [1, a]
:match
end
=> :match
irb> a
=> 2
```
Splat operator
```ruby
case [1, 2, 3, 4]
in [1, *a]
end
=> nil
irb> a
=> [2, 3, 4]
```
Skip some values
```ruby
case [1, 2, 3]
in [_, a, 3]
end
=> nil
irb> a
=> 2
```
Omit brackets
```ruby
case [1, 2, 3]
in 1, a, 3
end
=> nil
irb> a
=> 2
```
Complex structure of the Array
```ruby
case [1, [2, 3, 4]]
in [a, [b, *c]]
end
=> nil
irb> a
=> 1
irb> b
=> 2
irb> c
=> [3, 4]
```
Hash
Pattern matching in Hash
```ruby
case { foo: 1, bar: 2 }
in { foo: 1, baz: 3 }
:no_match
in { foo: 1, bar: b }
:match
end
=> :match
irb> b
=> 2
```
Double splat operator
```ruby
case { foo: 1, bar: 2, baz: 3 }
in { foo: 1, **rest }
end
=> nil
irb> rest
=> {:bar=>2, :baz=>3}
```
Omit brackets
```ruby
case { foo: 1, bar: 2 }
in foo: foo, bar: bar
end
=> nil
irb> foo
=> 1
irb> bar
=> 2
```
Syntactic sugar
```ruby
case { foo: 1, bar: 2 }
in foo:, bar:
end
=> nil
irb> foo
=> 1
irb> bar
=> 2
```
Exact match in array & Subset match in hash
Array
```ruby
case [1, 2]
in [1]
:no_match
end
Traceback (most recent call last):
4: from /home/agnieszka/.rvm/rubies/ruby-2.7.0-preview1/bin/irb:23:in `'
3: from /home/agnieszka/.rvm/rubies/ruby-2.7.0-preview1/bin/irb:23:in `load'
2: from /home/agnieszka/.rvm/rubies/ruby-2.7.0-preview1/lib/ruby/gems/2.7.0/gems/irb-1.0.0/exe/irb:11:in `'
1: from (irb):33
NoMatchingPatternError ([1, 2])
```
Hash
```ruby
case { foo: 1, bar: 2 }
in foo:
:match
end
=> :match
irb> foo
=> 1
```
The same behavior in Hash like for Array
```ruby
case { foo: 1, bar: 2 }
in foo:, **rest if rest.empty?
:no_match
end
Traceback (most recent call last):
4: from /home/agnieszka/.rvm/rubies/ruby-2.7.0-preview1/bin/irb:23:in `'
3: from /home/agnieszka/.rvm/rubies/ruby-2.7.0-preview1/bin/irb:23:in `load'
2: from /home/agnieszka/.rvm/rubies/ruby-2.7.0-preview1/lib/ruby/gems/2.7.0/gems/irb-1.0.0/exe/irb:11:in `'
1: from (irb):37
NoMatchingPatternError ({:foo=>1, :bar=>2})
```
Guards
Guard condition
```ruby
case [1, 2, 3]
in [a, *c] if a != 1
:no_match
in [a, *c] if a == 1
:match
end
=> :match
irb> a
=> 1
irb> c
=> [2, 3]
```
What can we use in pattern matching?
Literals
```ruby
case 2
in (1..3)
:match
in Integer
:too_late_for_match
end
=> :match
```
Variables
```ruby
irb> array = [1, 2, 3]
=> [1, 2, 3]
case [1, 2, 4]
in array
:match
end
irb> array
=> [1, 2, 4]
```
^ operator
```ruby
irb> array
=> [1, 2, 4]
case [1, 2, 3]
in ^array
:no_match
else
:match
end
irb> array
=> [1, 2, 4]
```
Alternative pattern
```ruby
case 5
in 6
:no_match
in 2 | 3 | 5
:match
end
=> :match
```
As pattern
```ruby
case [1, 2, [3, 4]]
in [1, 2, [3, b] => a]
end
=> nil
irb> a
=> [3, 4]
irb> b
=> 4
```
Pattern matching for others objects
Struct
```ruby
Point = Struct.new(:latitude, :longitude)
point = Point[50.29543618146685, 18.666200637817383]
case point
in latitude, longitude
end
=> nil
irb> latitude
=> 50.29543618146685
irb> longitude
=> 18.666200637817383
```
Pattern matching for custom objects
`deconstruct` or `deconstruct_keys`
Date
```ruby
class Date
def deconstruct_keys(keys)
{ year: year, month: month, day: day }
end
end
date = Date.new(2019, 9, 21)
case date
in year:
end
=> nil
irb> year
=> 2019
```
```ruby
point = JSON.parse(json, symbolize_names: true)
if point[:type] == "FeatureCollection"
features = point[:features]
if features.size == 1 && features[0][:type] == "Feature"
geometry = features[0][:geometry]
if geometry[:type] == "Point" && geometry["coordinates"].size == 2
longitude, latitude = geometry["coordinates"]
end
end
end
irb> longitude
=> 18.666200637817383
irb> latitude
=> 50.29543618146685
```
JSON with pattern matching
```ruby
case JSON.parse(json, symbolize_names: true)
in {
type: "FeatureCollection",
features: [{
type: "Feature",
geometry: {
type: "Point",
coordinates: [longitude, latitude]
}}]}
end
irb> longitude
=> 18.666200637817383
irb> latitude
=> 50.29543618146685
```
Scope strange behavior
Problem with scope
```ruby
case[1, 2]
in x, y if y > 3
:no_match
in x, z if z < 3
:match
end
=> :match
irb> x
=> 1
irb> z
=> 2
# unexpected assignment for y when pattern matching failed
irb> y
=> 2
```
What I would like to see?
It will be nice to have
- one line pattern matching
```ruby
case [1, 2, [3, 4]] { [1, 2, [3, b] => a] }
```
- calculations in patterns
```ruby
in (1..3).to_a
```
- allowed variables in alternative pattern
```ruby
[1, 2] | [1, 2, c]
```