Skip to content

Commit

Permalink
Update documentation for pattern matching
Browse files Browse the repository at this point in the history
  • Loading branch information
k-tsj committed Dec 21, 2020
1 parent 208f7d7 commit 5c0abe2
Showing 1 changed file with 37 additions and 18 deletions.
55 changes: 37 additions & 18 deletions doc/syntax/pattern_matching.rdoc
Original file line number Diff line number Diff line change
@@ -15,15 +15,15 @@ Pattern matching in Ruby is implemented with the +case+/+in+ expression:
...
end

(Note that +in+ and +when+ branches can *not* be mixed in one +case+ expression.)
(Note that +in+ and +when+ branches can NOT be mixed in one +case+ expression.)

or with the +=>+ operator and the +in+ operator, which can be used in a standalone expression:
or with the <code>=></code> operator and the +in+ operator, which can be used in a standalone expression:

<expression> => <pattern>

<expression> in <pattern>

Pattern matching is _exhaustive_: if variable doesn't match pattern (in a separate +in+ clause), or doesn't matches any branch of +case+ expression (and +else+ branch is absent), +NoMatchingPatternError+ is raised.
The +case+/+in+ expression is _exhaustive_: if the value of the expression doesn't match any branch of +case+ expression (and +else+ branch is absent), +NoMatchingPatternError+ is raised.

Therefore, +case+ expression might be used for conditional matching and unpacking:

@@ -39,7 +39,7 @@ Therefore, +case+ expression might be used for conditional matching and unpackin
end
# Prints: "Connect with user 'admin'"

whilst the +=>+ operator is most useful when expected data structure is known beforehand, to just unpack parts of it:
whilst the <code>=></code> operator is most useful when expected data structure is known beforehand, to just unpack parts of it:

config = {db: {user: 'admin', password: 'abc123'}}

@@ -48,7 +48,7 @@ whilst the +=>+ operator is most useful when expected data structure is known be
puts "Connect with user '#{user}'"
# Prints: "Connect with user 'admin'"

+<expression> in <pattern>+ is the same as +case <expression>; in <pattern>; true; else false; end+.
<code><expression> in <pattern></code> is the same as <code>case <expression>; in <pattern>; true; else false; end</code>.
You can use it when you only want to know if a pattern has been matched or not:

users = [{name: "Alice", age: 12}, {name: "Bob", age: 23}]
@@ -65,12 +65,12 @@ Patterns can be:
* find pattern: <code>[*variable, <subpattern>, <subpattern>, <subpattern>, ..., *variable]</code>; (<em>Find pattern</em>)
* hash pattern: <code>{key: <subpattern>, key: <subpattern>, ...}</code>; (<em>Hash pattern</em>)
* combination of patterns with <code>|</code>; (<em>Alternative pattern</em>)
* variable capture: <code>variable</code> or <code><pattern> => variable</code>; (<em>Variable pattern</em>, <em>As pattern</em>)
* variable capture: <code><pattern> => variable</code> or <code>variable</code>; (<em>As pattern</em>, <em>Variable pattern</em>)

Any pattern can be nested inside array/find/hash patterns where <code><subpattern></code> is specified.

Array patterns and find patterns match arrays, or objects that respond to +deconstruct+ (see below about the latter).
Hash patterns match hashes, or objects that respond to +deconstruct_keys+ (see below about the latter). Note that only symbol keys are supported for hash patterns, at least for now.
Hash patterns match hashes, or objects that respond to +deconstruct_keys+ (see below about the latter). Note that only symbol keys are supported for hash patterns.

An important difference between array and hash patterns behavior is arrays match only a _whole_ array

@@ -92,6 +92,24 @@ while the hash matches even if there are other keys besides specified part:
end
#=> "matched"

<code>{}</code> is the only exclusion from this rule. It matches iff an empty hash is given:

case {a: 1, b: 2, c: 3}
in {}
"matched"
else
"not matched"
end
#=> "not matched"

case {}
in {}
"matched"
else
"not matched"
end
#=> "matched"

There is also a way to specify there should be no other keys in the matched hash except those explicitly specified by pattern, with <code>**nil</code>:

case {a: 1, b: 2}
@@ -122,7 +140,7 @@ Both array and hash patterns support "rest" specification:
end
#=> "matched"

In +case+ (but not in +=>+ and +in+) expression, parentheses around both kinds of patterns could be omitted
In +case+ (but not in <code>=></code> and +in+) expression, parentheses around both kinds of patterns could be omitted:

case [1, 2]
in Integer, Integer
@@ -140,7 +158,7 @@ In +case+ (but not in +=>+ and +in+) expression, parentheses around both kinds o
end
#=> "matched"

Find pattern is similar to array pattern but it can be used to check if the given object has any elements that match the pattern.
Find pattern is similar to array pattern but it can be used to check if the given object has any elements that match the pattern:

case ["a", 1, "b", "c", 2]
in [*, String, String, *]
@@ -187,7 +205,7 @@ If no additional check is required, only binding some part of the data to a vari
end
#=> "matched: 1"

For hash patterns, even a simpler form exists: key-only specification (without any value) binds the local variable with the key's name, too:
For hash patterns, even a simpler form exists: key-only specification (without any sub-pattern) binds the local variable with the key's name, too:

case {a: 1, b: 2, c: 3}
in a:
@@ -235,17 +253,17 @@ Binding to variables currently does NOT work for alternative patterns joined wit
end
# SyntaxError (illegal variable in alternative pattern (a))

<code>_</code> is the only exclusion from this rule: it still binds the first match to local variable <code>_</code>, but allowed to be used in alternative patterns:
Variables that start with <code>_</code> are the only exclusions from this rule:

case {a: 1, b: 2}
in {a: _} | Array
"matched: #{_}"
in {a: _, b: _foo} | Array
"matched: #{_}, #{_foo}"
else
"not matched"
end
# => "matched: 1"

It is, though, not advised to reuse bound value, as <code>_</code> pattern's goal is to signify discarded value.
It is, though, not advised to reuse bound value, as these pattern's goal is to signify discarded value.

== Variable pinning

@@ -262,7 +280,7 @@ Due to variable binding feature, existing local variable can't be straightforwar
# expected: "not matched. expectation was: 18"
# real: "matched. expectation was: 1" -- local variable just rewritten

For this case, the pin operator <code>^</code> can be used, to tell Ruby "just use this value as a part of pattern"
For this case, the pin operator <code>^</code> can be used, to tell Ruby "just use this value as a part of pattern":

expectation = 18
case [1, 2]
@@ -294,9 +312,9 @@ One important usage of variable pinning is specifying the same value should happ
end
#=> "not matched"

== Matching non-primitive objects: +deconstruct_keys+ and +deconstruct+
== Matching non-primitive objects: +deconstruct+ and +deconstruct_keys+

As already mentioned above, hash and array/find patterns besides literal arrays and hashes will try to match any object implementing +deconstruct+ (for array/find patterns) or +deconstruct_keys+ (for hash patterns).
As already mentioned above, array/find and hash patterns besides literal arrays and hashes will try to match any object implementing +deconstruct+ (for array/find patterns) or +deconstruct_keys+ (for hash patterns).

class Point
def initialize(x, y)
@@ -315,7 +333,7 @@ As already mentioned above, hash and array/find patterns besides literal arrays
end

case Point.new(1, -2)
in px, Integer # subpatterns and variable binding works
in px, Integer # sub-patterns and variable binding works
"matched: #{px}"
else
"not matched"
@@ -418,6 +436,7 @@ So, only subsequently loaded files or `eval`-ed code is affected by switching th
Alternatively, command-line key <code>-W:no-experimental</code> can be used to turn off "experimental" feature warnings.

== Appendix A. Pattern syntax

Approximate syntax is:

pattern: value_pattern

0 comments on commit 5c0abe2

Please sign in to comment.