The match Control Operator

In this Page

Overview

The match operator allows you to choose an expression to be executed based on whether an input value matches a given pattern.  It can be very useful when you need to execute different expressions based on complex conditions on the input.  For example, if you wanted to transform a student's score into a letter grade, you would write a series of numeric range patterns with the corresponding expression being the letter grade string.  The result of the expression that was executed would then be the result of the whole match expression.  In addition to specifying the types and shape of incoming data, patterns can be used to store parts of the incoming data into variables for use in the expression.  The combination of matching multiple complex patterns and extracting elements with a straightforward syntax should make it the preferred control-flow operator over the ternary operator.

A match consists of an input expression and a body that contains one-or-more "arms".  Each arm consists of one-or-more patterns and a corresponding expression to execute.  When a match expression is evaluated, the input expression is evaluated and the result is checked against each match arm in order.  If the input value does not match any of the patterns in the match arms, the match will return null.

The following match expression computes the letter grade for a student's score.

Going through this example line-by-line:

  • The first line, "match $score", specifies that we want to perform a match on the value of  "$score"
  • Line 2 is a match arm with an "inclusive" range pattern.  It will match values greater than or equal to 90 and less than or equal to 100 and return an 'A' on a match.
  • Lines 3-5 are match arms with "exclusive" range patterns.  They will match values that are greater than or equal to their lower bound and less than their upper bound.  For example, if a score was 89.5, the result of the expression would be a 'B'.
  • Line 6 uses an underscore to specify a default, "catch-all", pattern.  Any values that do not match the earlier patterns will match this one and result in an 'F'.


The following example computes the letter grade for a score using the ternary operator.  Nested ternaries can be hard to read and write correctly as the number of cases grow.  They also require a lot of repetition since the value being tested needs to be repeated for each check.

The following is a more complex example that tries to find the 'Home' phone number for a person in their list of numbers.  It uses an array pattern with wildcards (...) to specify that the element we're looking for can be at any index in the array and an object pattern to identify the element where the "type" property has the value "Home".

Syntax

A match expression starts with the "match" keyword, followed by the input value and a block of match "arms".  Each arm specifies one or more patterns to match against the input and the expression to execute on a successful match separated by a fat-arrow (=>).  The pattern for a match arm can also have a "guard expression" for cases where the supported set of patterns is not enough to fully test the input value.  For example, if you wanted to test that a number was divisible by five, you would write a pattern like:


The full syntax for the match expression is as follows:

Patterns

The patterns passed to the match operator allow you to describe the shape and, optionally, the content of the input values.  The match operator will try each pattern in turn until a full match is found.  The patterns can be simple constants, like integers, or they can describe an element to be found in an array.

Constants

Known input values can be matched using a constant.  The supported constants are as follows:

  • Primitives

    • true / false / null / NaN
  • Integers
    • Example: 1
  • Strings
    • Example: "Testing"
  • Objects
    • Example: { last_name: "Targaryen" }
  • Arrays

    • Example: ["abc", "def"]

Binding Variables

Unknown values can be captured into variables for use in the corresponding expression and/or the "guard" expression.  Note that variables must be used in the corresponding expression or an error will be raised.  If the variable is needed, but is not used in an expression you can prefix it with an underscore (_) to silence the error.  Use a single underscore as the pattern for the last arm when you want to provide a default value if none of the other arms matched.  If an exclamation mark (!) is appended to the variable name, it must not be null or an empty string.

Here are some patterns that bind input values to variables:

  • value - Captures the input value.
  • [first, second, third] - Captures the elements of an array into the variables: first, second, and third.

If you're using a pattern that can match multiple values and you need to use the full input value in the expression, you can prefix the pattern with "<variable-name> @".  For example:

score @ 90..=100 => "High score(!): " + score

Matching Strings

Unknown string values can be matched using regular expressions or by matching just their prefix or suffix using the following syntax:

  • /<regular-expression>/ - Checks if the input is a string and checks it against the given regular expression pattern.
  • [variable-name]..."<suffix>" - Checks if the input is a string and has the given suffix string.  Optionally, the leading part of the string can be captured into a variable for use in a guard or corresponding expression.  If an exclamation mark (!) is appended to the variable name, the captured string must not be empty.
  • "<prefix>"...[variable-name] - Checks if the input is a string and has the given prefix string.  Optionally, the trailing part of the string can be captured into a variable for use in a guard or corresponding expression.  If an exclamation mark (!) is appended to the variable name, the captured string must not be empty.

Number Ranges

Unknown number values can be matched against a range using the following syntax:

  • |lower-bound|..=|upper-bound| - Inclusive upper-bound. This version will match numbers that are greater than or equal to the lower-bound and less-than-or-equal-to the upper-bound.
  • |lower-bound|..<|upper-bound| - Exclusive upper-bound. This version will match numbers that are greater than or equal to the lower-bound and less-than the upper-bound.
  • |lower-bound|.. - Minimum number. This version will match numbers that are greater than or equal to the lower-bound.

The bounding values can only be integers due to the difficulty in comparing decimal numbers correctly.  However, the input value can be a decimal.  For example, the range 1..<2 will match the integer 1 and the decimal value 1.5.

Objects

Unknown objects can be checked to see if they contain a given set of properties and if their property values match a given pattern.  Object patterns are written like object literals where the property values are themselves patterns.  The pattern will only match the input if the given properties are found in the input object (except if the '?' flag is used on the property name) and the property value matches the corresponding pattern.  If the input object contains properties that are not found in the pattern, they are ignored and the object pattern will still match.

To make it easier to write object patterns, the following shortcuts are also supported:

  1. Property values are optional and the value of the property will be captured in a variable with the same name as the property.  For example, to capture the "first_name" and "last_name" properties in an object into variables with the same names, you can write:
    • { first_name, last_name } => first_name + " " + last_name
  2. Appending an exclamation mark (!) to a property name marks it as required, meaning its value must not be null or an empty string.  Taking the previous example, if we wanted to ensure that the first_name and last_name properties were meaningful, we could change the pattern to the following:
    • { last_name!, first_name! } => first_name + " " + last_name
  3. Appending a question mark (?) to a property name marks it as optional, meaning it can be missing from the input object and the value of the variable will be set to null.  Adding on to the previous example, if there was an optional "nick_name" field in the input object, we could use it in the expression if it was available or fall back to the first_nameproperty.
    • { last_name!, first_name!, nick_name? } => (nick_name || first_name) + " " + last_name

Variably-Sized Arrays

Arrays of unknown size can be matched and their elements extracted using wildcards (...) in array patterns.  When placed at the start of an array pattern, the pattern will scan the array to find an element that matches the next pattern in the array pattern.  When placed at the end of an array pattern, the pattern will match any remaining values in the array.  To capture the elements that are matched by a wildcard, you can append a variable name to the pattern (e.g. ...tail).

  • [head, ...tail] - Captures the first element into the "head" variable and all of the remaining elements into the "tail" variable.
  • [...head, tail] - Captures the last element into the "tail" variable and all of the leading elements into the "head" variable.
  • [...head, "|", ...tail] - Splits an array around the pattern in the middle (a string with a pipe symbol in this case) and stores the elements before and after the split into the "head" and "tail" variables, respectively.
  • [..., {type: "Home", value}, ...] - Scans the array to find the element that matches the pattern in the middle.  In this example, the element to match has a property named "type" with the value of "Home" and a "value" property that should be captured.

It is an error to place wildcards next to each other since there is no way for the pattern to figure out where one wildcard ends and the next begins.  If an array pattern has no wildcards, it will only match input arrays that have the same number of elements as the array pattern.

Combining Patterns

Unknown input values can be matched against multiple patterns by joining the patterns with a pipe symbol (|).  For example, to match the strings "foo" or "bar", you would write the pattern as:





If you need to capture the input value to use in the expression, you can prefix the pattern with the syntax "<variable-name> @", like so: