Skip to content
impworks edited this page Oct 6, 2014 · 9 revisions

Pattern matching is a powerful feature found in many contemporary functional programming languages. It presents a conscise and clear way of decomposing complex data structures and performing checks on them, akin to what RegExps are for strings.

The match expression

The match expression consists of an expression to check and a set of rules, each optionally returning an expression:

var x = rand 1 10
match x with
    case 1 then "one"
    case 2 then "two"
    case 3 then "three"
    case _ then "some other number"

Match expressions are evaluated one by one from top to bottom. If any of them matches, the result is returned and no further rules are checked. If no rules match, then the default(T) value is returned. No exception is thrown.

Match rules

LENS provides several useful rules for matching the expressions:

1. Literal rule

Literals can be of any built-in types: int, string, bool, float, etc. The null value is also a literal.

The rule matches if the expression equals the specified literal.

2. Name binding

Any identifier found in the rule is a name binding. The matched expression is captured into a variable which is available in the expression that is returned by the rule:

match 1 with
    case x then fmt "the value is {0}" x

The underscore character (_) is special: it means we do not care about the actual value of the expression. It can be placed as many times as needed and is commonly used as the 'catch-all' expression.

Name binding can provide an explicit type signature. Then it will only match if the expression is really of the specified type:

var ex = getException ()
match ex with
    case x:ArgumentException then "Arguments are invalid"
    case x:DivideByZeroException then "Division by zero"
    case _ then "Something went wrong"

3. Range rule

A numeric value can be checked for being in a range. The range borders are inclusive:

var x = rand 1 10
match x with
    case 1..5 then "less than five"
    case 6..8 then "six, seven, eight"
    case _ then "nine or more"

4. Tuples

Tuple can be decomposed into its elements, applying a rule to each item:

let tuple = new (1; 2; "test")
match tuple with
    (x; y; str) -> fmt "{0} = {1}" str (x + y) // test = 3

5. Arrays and sequences

Arrays and sequences can be split into items:

match array with
    case [] then "empty"
    case [x] then fmt "one item: {0}" x
    case [x; y] then fmt "two items: {0} and {1}" x y
    case [_; _; _] then "three items"
    case _ then "incountable multitude of items"

One of the elements can be specified with the ... prefix. This will mean that it matches not just one item, but a whole subsequence containing zero or more items:

fun length:int (array:object[]) ->
    match array with
        case [] then 0
        case [_] then 1
        case [_; ...x] then 1 + (len x)

For arrays arrays, IList<T> and other collections of finite size, the subsequence can be defined at any position in the rule. For IEnumerable<T> objects, however, the subsequence can only be defined as the last item of the rule.

6. Records

Records and types defined in the LENS script can be decomposed into fields (or label):

record Point
    X : int
    Y : int

fun describe:string (pt:Point) ->
    match pt with
        case Point(X = 0; Y = 0) then "Zero"
        case Point(X = 0) | Point(Y = 0) then "half-zero"
        case _ then "Just a point"

Only the values of specified fields are checked. Fields that are not specified will not be checked.

7. Types

Types with labels can also be checked and decomposed:

type Expr
    IntExpr of int
    StringExpr of string
    AddExpr of Tuple<Expr, Expr>
    SubExpr of Tuple<Expr, Expr>

fun describe:string (expr:Expr) ->
    match expr with
        case IntExpr of x then fmt "Int = {0}" x
        case StringExpr of x then fmt "Str = {0}" x
        case AddExpr of (x; y) then fmt "Sum = {0}" ((describe x) + (describe y))
        case SubExpr of (x; y) then fmt "Subtraction = {0}" ((describe x) - (describe y))

Please note that the type pattern requires the of keyword and the existance of a tag. For type labels without a tag the name pattern with explicit type specification is used.

8. KeyValue rule

To match a dictionary entry, use the special => syntax:

match keyValue with
    case key => value then fmt "key = {0}; value = {1}" key value

Alternate rules

Several rules can be stacked up to yield the same expression using the vertical bar:

match number with
    case 1 | 2 | 3 then "one, two or three"
    case _ then "other number"

If there are any name bindings, they must match exactly in all of the alternatives in both name and type.

When guards

Rules can also be augmented by arbitrary expressions which must evaluate to true for the rule to match. The when keyword is used:

match x with
    case y when y % 2 == 0 then "even"
    case _ then "odd"
Clone this wiki locally