# Pattern Matching Tenets
1. Patterns should be _[concise][]_
2. Patterns should be _[expressive][]_
3. Patterns should be _[explicit][]_
4. Patterns should be _[extensible][]_
5. Pattern matching should be _[exhaustive][]_
# Tenet 1 — Patterns should be _concise_
The primary goal of pattern matching is to **reduce the overhead incurred by non-pattern based control flow**. This
means simplifying multi-statement or multi-expression comparisons to improve readability, as well as to cut down on the
need to introduce temporary variables used only to avoid recomputing potentially side-effectful or expensive code.
Consider the following ECMAScript code sample:
```js
if (obj.dimensions.height === 10 && obj.dimensions.width === 10) { … }
```
**Example 1 —** A multi-step conditional. _(ECMAScript)_
In this example, the `dimensions` property could potentially be an accessor that is side-effectful, or may
perform an expensive computation. As such, we may want to avoid re-evaluating `obj.dimensions` and might instead use a
variable to store the partial computation:
```js
const dimensions = obj.dimensions;
if (dimensions.height === 10 && dimensions.width === 10) { … }
```
**Example 2 —** A multi-step conditional with a temporary variable. _(ECMAScript)_
However, this further increases the complexity of the condition by adding an additional statement, and requires the
naming of an otherwise unused variable.
Now consider the same condition using the pattern matching syntax from C#:
```cs
if (obj is { dimensions: { height: 10, width: 10 } }) { … }
```
**Example 3 —** Nested pattern matching. _(C#)_
The pattern matching example avoids re-execution of the property access from **example 1**, as well as the need to
introduce a temporary variable like in **example 2**.
_Patterns should be concise_. However, for a pattern to be adequately concise, the pattern syntax must also be
_[expressive][]_.
# Tenet 2 — Patterns should be _expressive_
In order for a pattern to be _[concise][]_, the pattern syntax must be expressive enough to represent the most
common cases of multi-step conditionals. There are a number of ways patterns can support such expressivity:
- Literal constant patterns that express `===` or SameValue/SameValueZero equality.
- Nested property patterns that can reduce the need for temporary variables and repeated use of `&&`.
- List patterns that can reduce the need for complex iteration and caching logic, as well as avoid opaque index accesses
like `x[0][1]`.
- Type-family patterns that test whether a value is an instance of a specific type.
- Relational patterns that can express other relational operators (i.e., `>`, `>=`, `<`, `<=`), or even ranges.
- Logical patterns that allow for conjection, disjunction, negation, and grouping.
Most languages with pattern matching support many of the above capabilities:
```cs
input switch {
0 => …, // literal constant
{ x: 0, y: 0 } => …, // nested properties
(1, 2, 3) => …, // tuples
[1, 2, 3, ..] => …, // lists
Rect => …, // type family
>1 => …, // relational
not null => …, // logical negation
1 or 2 or 3 => …, // logical disjunction
>0 and <3 => …, // logical conjunction
(>0 or >0F) and (<100 or <100F) => … // grouping
}
```
**Example 4 —** Expressivity in C# `switch` expressions. _(C#)_
```fs
match input with
| 0 -> … // literal constant
| { x = 0; y = 0; } -> … // nested properties
| (1, 2, 3) -> … // tuples
| [1; 2; 3] -> … // lists
| [|1; 2; 3 |] -> … // arrays
| p : Point -> … // type family
| :? Point -> … // type family
| 1 | 2 | 3 -> … // logical disjunction
| (a, b) & (_, "test") -> … // logical conjunction
| (1 | 2) & (2 | 3) -> … // grouping
| …
```
**Example 5 —** Expressivity in F# `match` expressions. _(F#)_
```rs
match input {
0 => …, // literal constant
Point { x: 0, y: 0 } => …, // nested properties
(1, 2, 3) => …, // lists/tuples
Point { .. } => …, // type family
0..=5 => …, // range
1 | 2 | 3 => …, // logical disjunction
…
}
```
**Example 6 —** Expressivity in Rust `match` expressions. _(Rust)_
```scala
input match
case 0 => … // literal constant
case Some(value) => … // nested properties (case classes and extractors)
case (1, 2, 3) => … // tuple pattern
case 1::2::3 => … // list pattern (infix operator and extractors)
case p: Point => … // type family
case 1 | 2 | 3 => … // logical disjunction
…
```
**Example 7 —** Expressivity in Scala `match` expressions. _(Scala)_
_Patterns should be expressive_. However, there are often either complex or niche cases where patterns cannot be
sufficiently expressive. In such cases, patterns should also be _[extensible][]_. And while patterns should be
sufficiently expressive to handle the majority cases, their syntax should also be unambiguous. For this reason, patterns
must also be _[explicit][]_.
# Tenet 3 — Patterns should be _explicit_
Because patterns should be _[concise][]_ and _[expressive][]_, we must take care that we do not overload the meaning of
various syntactic constructs. This would introduce confusion to readers and result in potentially unexpected behavior.
To avoid this, patterns should be _explicit_ — there should be no ambiguity in the meaning of a particular
pattern.
For example, consider a list pattern like `[A, B]` (or `(A, B)`, depending on the language). Should this pattern
introduce new variables `A` and `B` corresponding to the first and second elements of the input? Or should it instead
resolve the values stored in the existing `A` and `B` variables and match them against the corresponding elements of the
input?
In C#, the pattern `(A, B)` is unambiguous and can only indicate references to existing types. The syntax related to
introducing new variables requires a type prefix or `var` keyword. `(A, B)` matches a tuple whose elements are the
types `A` and `B`, respectively. `(A a, B b)` matches a tuple with elements of type `A` and `B` and stores those values
into the variables `a` and `b`, respectively. `(var a, var b)` matches a tuple regardless of the types of the two
elements, and introduces the variables `a` and `b`.
Depending on the underlying syntax, such collisions can be avoided. For ECMAScript, there are a number of options to
explore:
- Leveraging the existing `let`, `const`, and `var` keywords (and potentially the proposed
[`using` keyword](https://github.com/tc39/proposal-explicit-resource-management)), similar to C#'s `var` and type
binding patterns.
- Introducing novel syntax for explicitly named bindings, such as Rust's `@` binding operator.
- Dislocating the bindings introduced from the pattern itself.
We must take care that whatever syntax we choose aligns as closely as possible with our preceding core tenets, namely:
- Being _explicit_ should not unduly prevent us from remaining _[concise][]_.
- Being _explicit_ should not unduly prevent us from remaining _[expressive][]_.
# Tenet 4 — Patterns should be _extensible_
While patterns should ideally be sufficiently _[expressive][]_, there are often more complex or niche cases that cannot
be readily handled by pattern syntax. To address this, patterns should be _extensible_ — providing escape hatches
to address these cases.
There are several mechanisms for providing _extensibility_:
- _[Pattern guards](#pattern-guards)_, which introduce additional conditions to evaluate to match a specific case.
- _[Custom matchers](#custom-matchers)_, which augment pattern matching behavior either through custom operators or a
dynamic API.
- _[Interpolation](#interpolation)_, which escapes from the pattern syntax to access runtime values or custom matchers.
## Pattern Guards
In cases where the pattern syntax is insufficiently _[expressive][]_, many languages with pattern matching also introduce
_pattern guards_ which provide additional expressivity at the cost of dislocation of the guard from the pattern itself:
```cs
input switch {
(var a, var b) when a > 0 && b > 0 => …, // pattern guard using 'when'
…
}
```
**Example 8 —** `when` pattern guard in C# `switch` expressions. _(C#)_
```fs
match input with
| (a, b) when a > 0 && b > 0 -> … // pattern guard using 'when'
| …
```
**Example 9 —** `when` pattern guard in F# `match` expressions. _(F#)_
```rs
input match {
(a, b) if a > 0 && b > 0 => … // pattern guard using 'if'
}
```
**Example 10 —** `if` pattern guard in Rust `match` expressions. _(Rust)_
```scala
input match
case (a, b) if a > 0 && b > 0 => … // pattern guard using 'if'
…
```
**Example 11 —** `if` pattern guard in Scala `match` expressions. _(Scala)_
While pattern guards are useful for handling niche cases, a truly _[expressive][]_ pattern syntax primarily favors a
more comprehensive set of patterns that promote locality of conditions to improve readability.
## Custom Matchers
In addition to _[pattern guards](#pattern-guards)_, pattern matching languages often introduce extensibility mechanisms
through a dispatch mechanism. In C#, this is handled via the
[`Deconstruct`](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/deconstruct) method, which allows
for the evaulation of user-defined behavior during destructuring and matching. In F#, this is addressed by
[Active Patterns](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/active-patterns), which allow you
to reference a user-defined function to customize matching behavior. In Scala, this is supported via
[Extractor Objects](https://docs.scala-lang.org/tour/extractor-objects.html )
and infix operator patterns.
```cs
class Person {
public string GivenName;
public string FamilyName;
public Person(string givenName, string familyName) {
GiveName = givenName;
FamilyName = familyName;
}
public void Deconstruct(out string givenName, out string familyName) {
givenName = GivenName;
familyName = familyName;
}
}
input match {
Person(var givenName, var familyName) => … // checks type and calls Deconstruct
}
```
**Example 12 —** Pattern extensibility via `Deconstruct`. _(C#)_
```fs
let (|RGB|) (col : System.Drawing.Color) = ( col.R, col.G, col.B )
match col with
| RGB(r, g, b) -> … // invokes RGB with input and destructures resulting tuple
| …
```
**Example 13 —** Pattern extensibility via Active Patterns. _(F#)_
```scala
object CustomerID {
def unapply(customerID: String): Option[String] = {
val stringArray: Array[String] = customerID.split("--")
if (stringArray.tail.nonEmpty) Some(stringArray.head) else None
}
}
input match
case CustomerID(name) => … // Invokes CustomerID.unapply
```
**Example 14 —** Pattern extensibility via Extractor Objects. _(Scala)_
```scala
// NOTE: exists in standard library
object :: {
def unapply[A](ls: List[A]): Option[(A, A)] = {
if (ls.empty) None
else Some((ls.head, ls.tail))
}
}
input match
case h::t => … // Looks up `::` operator, invokes `unapply` and destructures result into `h` and `t`
```
**Example 14 —** Pattern extensibility via Extractor Objects and infix operator patterns.
_(Scala)_
## Interpolation
One last extensibility mechanism to consider is _interpoloation_. This is an escape hatch from the pattern syntax that
allows you to substitute the result of evaluating an expression into a pattern. Such a mechanism is not widely employed
in any language, however.
# Tenet 5 — Pattern matching should be _exhaustive_
Statement-based control flow in ECMAScript often allows you to elide branches. You can have an `if` without an `else`
and a `switch` without a `default` and this is acceptable within the language itself because these statements can
simply produce an empty _completion record_ for unhandled cases. However, unhandled branches are frequently a source of
bugs in software and many static analysis tools often implement _control flow analysis_ to find and surface such issues.
Expression-based control flow in ECMAScript differs in that it does not allow you to elide branches. A conditional
expression cannot elide the "falsy" branch, and operators like `&&`, `||`, and `??` do not allow you to elide the
right-hand of the expression. Even operators like `?.` are essentially exhaustive as they are often just a shorthand
for a conditional operation whose "falsy" branch is just `undefined`.
Exhaustiveness is achieved in pattern matching through the use of _refutable patterns_ and _irrefutable patterns_. A
_refutable pattern_ is one that conditonally matches a value. If such a pattern does not match, the next alternative or
branch should be attempted. An _irrefutable pattern_ is one that will always match. For a pattern matching construct
to be considered _exhaustive_ it must either cover the input domain with sufficient _refutable patterns_, or it must
also have an _irrefutable pattern_.
For example, in Rust every variable declaration is essentially a pattern. The `x` in the statement `let x = 5` is an
_irrefutable pattern_, as there is no input value that would not match. However, the `Some(x)` in the statement
`let Some(x) = opt` is a _refutable pattern_: if `opt` is not a `Some`, the pattern will not match and an error will
be reported.
This is not unlike destructuring in ECMAScript today. The statement `let { x } = input` is an example of a _refutable
pattern_: if `input` is not an object, the statement will throw. In the same vein, `let [x] = input` is also a
_refutable pattern_: if `input` does not have a `[Symbol.iterator]` method, the statement will throw.
One way we could implement _exhaustiveness_ in ECMAScript pattern matching would be to also throw if some branch of the
pattern fails to match. For example, if we were to replace the simple conditional `a ? b : c` with a `switch`
expression in C#, it might look something like this:
```cs
a switch {
true => b,
false => c
}
```
**Example 15 —** Exhaustiveness in a `switch` expression. _(C#)_
If we were to elide either branch, C#'s control flow analysis would fail, not unlike if we'd written `a ? b :` or
`a ? : c`.
Event without static typing we can still support _exhaustiveness_ in ECMAScript at runtime similar to how we support it
for destructuring, by throwing a `TypeError`. In addition, third-party static analysis tools such as TypeScript and Flow
would be able to recognize non-exhaustive pattern matching expressions and provide early warnings of potential errors.
The one instance where exhaustiveness should not be required is when the intent is to test _whether_ the pattern matched
rather than _what_ was matched. In C#, the `is` expression can be used to conditionally test a pattern and returns
either `true` if the pattern was exhaustive, or `false` if it was not:
```cs
if (obj is int) { ... }
```
**Example 16 —** Non-exhaustive pattern matching via the `is` expression. _(C#)_
Similarly, in Rust you can use `if let` and `while let` to conditionally match a non-exhaustive pattern:
```rust
if let Some(x) = opt { ... }
```
**Example 17 —** Non-exhaustive pattern matching via the `if let` statement. _(Rust)_
_Pattern matching should be exhaustive_, as exhaustiveness helps to avoid defects.
[concise]: #tenet-1--patterns-should-be-concise
[expressive]: #tenet-2--patterns-should-be-expressive
[explicit]: #tenet-3--patterns-should-be-explicit
[extensible]: #tenet-4--patterns-should-be-extensible
[exhaustive]: #tenet-5--pattern-matching-should-be-exhaustive