Patterns

If you've seen a few lines of Magpie code, you've likely seen patterns already. They are used everywhere in the language: match expressions obviously use them, but so do variable declarations, method parameters, and catch clauses for handling exceptions.

Patterns are the foundation that control flow and naming are built on in Magpie. Given an object, a pattern does two things: First, it tests if the object matches that pattern. Then, if and only if it does, it may bind new variables to parts of the object. By performing those operations together, patterns can pull data out of an object but only when the object correctly has the data you're asking for.

A quick example in the context of a match expression should clarify:

match "s", 234, true
    case "s", n is Int, _ then print(n + 2)
end

Here, the value being matched is the record "s", 234, true. The pattern is "s", n is Int, _, a record pattern containing a value pattern, type pattern, and wildcard pattern, respectively. This pattern does match that value, which means it will bind n and then evaluate the body in that scope. In this case, the body is just print(n + 2).

Kinds of Patterns

Syntactically, patterns are a bit like the twin of expressions. Like expressions, there are different atomic pattern syntaxes which can be composed to form larger patterns. There are six kinds of patterns:

Literal Patterns

A literal value like 123 or true where a pattern is expected defines a literal pattern. As you would expect, a literal pattern only matches an identical value. The pattern "hi" matches the string value "hi" and nothing else.

Equality Patterns

To check if a value is equal to the result of some expression, you can use an equality pattern. It starts with ==, followed by the value to be compared with.

== pi

The above pattern will match the value π and fail to match otherwise.

Type Patterns

Now we start to get to the interesting patterns. Often, you'll want to check to see if a value is of a certain class (or a subclass) in order to tell if an operation is valid. To do that, you can use a type pattern. A type pattern starts with the keyword is followed by an expression:

is String

A type pattern matches if the value is of the given class or one of its subclasses.

Variable Patterns

To bind values to names, you use variable patterns. A variable pattern always successfully matches, and when it does, it creates a new named variable whose value is the matched object. As you'd expect, a variable pattern is just an identifier.

name

This pattern matches any value and creates a new variable named name when it does. A variable pattern may also have another pattern following it. If it does, the variable pattern will only match if that pattern matches too. For example:

name is String

This is a variable pattern containing a type pattern. The entire pattern matches only if the value is a string. If it is, then it will bind the variable name to the value.

Wildcard Patterns

If the name in a variable pattern is _, then it's a wildcard pattern. It works exactly like a variable pattern except that no variable will actually be created. This can be useful if you want to say "a value goes here" but you don't care what the value is. Like other variable patterns, it may also have an inner pattern.

Record Patterns

Record patterns are the dual to record expressions. A record pattern contains a series of fields. Each field may have a name, and must have a pattern. When it is tested, it looks for fields in the given value to match all of the pattern's fields. The entire record pattern matches if all of its field patterns match.

x: _, y: _

This will match any record with fields x and y. This is using simple wildcard patterns for the fields, but more complex patterns can be used:

x: 1, y: is String

This will match a record whose x field is 1 and whose y field contains a string. By using variable patterns for the fields, a record can be destructured into its component parts.

name: n, address: a

This will match a record with name and address fields. If it matches, it will create new variables n and a and bind them to the values of those fields.

Like record expressions, record patterns can omit the field names, in which case they'll be inferred by position:

x is Int, y is Int

This matches a record with two positional fields whose values are integers and binds the fields. In other words, matching that pattern against 3, 4 will bind x to 3 and y to 4.