Note: None of this capability is currently implemented.

As with if, pattern matching is about deciding which code path to perform. Its approach is to match a single value against multiple patterns. The code block associated with the first matching pattern is performed.

Let's illustrate this with a simple (but mathematically unusual) example:

fn power(n i32, pow i32) i32
  match pow
    0: 1
    1: n
    2: n * n
    _: -1

This example calculates its value differently based on the value of pow. If pow is zero, we return 1. If pow is 1, we return n. If pow is two, we return n squared. Finally, if pow is any other value, we return -1.

The structure of this form of pattern matching is straightforward:

A versatile range of patterns are supported, able to match wholly to specified values, partially, or using a first-class pattern. Pattern matching also supports de-structuring portions of the original value into bound variables.

Whole Value Matches

These patterns involve straightforward comparisons between the matchee value and one or more pattern values.

Equivalence

The simplest form of matching uses a value as the pattern. This can be a constant value (as above) or any expression that evaluates to some value, for example:

match val
  anotherval.len:  4
  _:               0

The type of the pattern value must match the type of the matchee value. An equivalence match is performed between these values as if using the '==' operator.

Comparisons

Comparison patterns make use of the comparison operators (including !=). The left hand value is not specified, as it is understood to be the match value:

match n
  <0: -1
  >0:  1
  _:   0

Range pattern

The range pattern checks whether the matchee value lies within a specified range. This is specified by placing either the .. or ... range operator between two expressions:

match n
  0 ..  3: 1   // matches 0, 1, or 2 (but not 3)
  3 ... 6: 2   // matches 3, 4, 5 or 6 inclusively

Pattern Expressions

Before continuing on to talk about partial pattern matching, let's introduce ways to enhance the match patterns already described as part of a more comprehensive pattern expression.

Multiple patterns

It is possible to list more than one possible pattern to match, using or between each pattern:

match n
  2 or 3 or 5 or 7:  2
  -6 or -1...1 or 6: 1
  _:                 0

Tuples

Multiple values to be matched can be specified on the match statement as a comma-separated tuple or with a function that returns multiple values. If a tuple is specified on the match, then all match patterns must be specified as tuples. For a tuple to match, all values must match individually:

match key,value
  0, _:  0
  _, 0:  1
  _, _:  2

if Guards

An 'if' clause may be specified following all patterns as an additional guard. The condition expression after the 'if' must evaluate to true in order for the match to take place.

match nbr
  4 if cnt > 0: 0  // matches only if nbr==4 and cnt>0
  4:            1  // matches when nbr==4 and cnt<=0
  _:            2  // matches when nbr != 4

Partial matches

When dealing with a composite data structure, partial matching supports value or type matching on selected parts of that data. A partial match pattern mimics the type structure of the data we are matching against, specifying match patterns for the value or type of particular substructural elements. It also supports "destructuring", binding variables to these or other substructural elements.

Partial match patterns begin with the ~~ operator. Additional specifics of the pattern vary by type.

Collection partial matches

The simplest partial match checks whether an array (or array reference) begins with certain elements:

match name
	~~ "Judy": judy     // Matches if name string begins with "Judy"
	~~ "Frank": frank
	_: noone

Alternatively, one can specify matching patterns for the first few elements:

match numbers
	~~ [1, _]: 1
	~~ [_, 1]: 2
	~~ [<=0, <=0]: 3
	_: 4

Struct partial matches

A matching pattern may be created for specific named fields:

match points
  ~~ Point[0., _]: 1
  ~~ Point[_, 0.]: 2
  ~~ Point[<0., <=0.]: 3
  _: 4

Any unspecified fields in a struct pattern are assumed to match.

Variant type partial matches

An variant pattern typically just matches against each option's variant type, binding to a variable:

match oddval
	~~ intval = SomeInt:     intval
	~~ point = PointStruct:  i32[point.x * point.y]
	None:                    0

If desired, pattern matching and binding can also be applied to the internal value(s):

match oddval
  ~~ intval = SomeInt[<0]:   -intval
  ~~ intval = SomeInt:       intval
  ~~ PointStruct[x=_, y=_]:  i32[x * y]
  None:                      0

First-class patterns

Whole and partial matching patterns handle many situations, but not all. First-class patterns make it possible to support more sophisticated matching algorithms. Such algorithms are implemented in a callable function or method that returns true for a successful match.

Use of a first-class pattern is signaled using the using keyword. The matchee value is implicitly included as part of the call (essentially, the first parameter for a function and the second for a method call):

match n
  using isPrime(): 0  // invokes isPrime(n)
  using isOdd():   1  // invokes isOdd(n)
  _:               2

This technique can be quite powerful, as it makes it possible to support a broad range of complex pattern matching strategies, such as regular expressions, parsing, etc. A first-class pattern can be an object with state that changes as a result of a successful match. As part of the match, data can be extracted from the matchee value and preserved for later use by the match block.

Note: A first class pattern can also be applied to any arbitrary value as part of an if statement:

++nbr if isOdd(n)

_