Pattern matching

Monte comes with a powerful and extensible subsystem for destructuring and viewing objects. A pattern is a rule which conditionally matches objects and binds parts of the matched objects to names.

pattern

postfixPatt

postfixPatt

SuchThatPatt prefixPatt

prefixPatt

MapPatt ListPatt SamePatt NotSamePatt QuasiliteralPatt ViaPatt IgnorePatt namePatt

namePatt

FinalPatt VarPatt BindPatt SlotPatt BindingPatt

The Such-That Pattern

SuchThatPatt

prefixPatt ? ( expr )

Non associative.

The such-that pattern contains a subpattern and a condition, not unlike the condition expression in an if expression. The such-that pattern first speculatively performs the pattern match in its subpattern, and then succeeds or fails based on whether the condition evaluates to true or false:

>>> def players := [object alice{}, object bob{}]
...
... object game:
...     to vote(player ? (players.contains(player)),
...             choice ? (players.contains(choice))) :
...        return "voted"
...
... def t1 := game.vote(players[0], players[1])
... def t2 := try { game.vote(object alice{}, "bob") } catch _ { "BZZT!" }
... [t1, t2]
["voted", "BZZT!"]

SuchThat Expansion

>>> m`def patt ? (condition) := value`.expand()
m`def via (_suchThat) [patt, via (_suchThat.run(condition)) _] := value`

List, Map Patterns

ListPatt

[ pattern , ] + pattern

MapPatt

[ mapPattItem , ] | pattern

mapPattItem

LiteralExpr ( expr ) => pattern => namePatt := order

List patterns match lists, matching each subpattern against the items in the list:

>>> def [x, y] := [5, 10]; x
5

If + rest is used, a list pattern of size N is matched against the first N items in the list, and the rest pattern is matched against the remaining items.:

>>> def [first] + rest := [1, 2, 3, 4]
... rest
[2, 3, 4]

If + is not used, the list pattern only matches lists of the same size

Map patterns match maps. Keys are either literal strings or expressions in parentheses. The subpatterns are matched against the values for the keys:

>>> def sides := ["square" => 4, "triangle" => 3]
... def shape := "triangle"
...
... def ["square" => squareSides, (shape) => qty1] := sides
...
... def ["triangle" => qty2] | _ := sides
...
... [squareSides, shape, qty1, qty2]
[4, "triangle", 3, 3]

‘:=’ may be used to specify a default value to match a subpattern against if the key is absent:

>>> def sides := ["square" => 4, "triangle" => 3]
...
... def ["octogon" => octoSides := 8] | _ := sides
... octoSides
8

The importer syntax without keys is a shortcut for binding names identical to string keys in a map:

>>> def sides := ["square" => 4, "triangle" => 3]
...
... def [=> triangle, => square] := sides
... [triangle, square]
[3, 4]

List Pattern Expansion

>>> m`def [item1, item2] + rest := stuff`.expand()
m`def via (_splitList.run(2)) [item1, item2, rest] := stuff`

Map Pattern Expansion

>>> m`def ["key" => patt] := data`.expand()
m`def via (_mapExtract.run("key")) [patt, _ :_mapEmpty] := data`

>>> m`def ["key1" => patt1] | rest := data`.expand()
m`def via (_mapExtract.run("key1")) [patt1, rest] := data`

>>> m`def ["key1" => patt1 := fallback] := data`.expand()
m`def via (_mapExtract.withDefault("key1", fallback)) [patt1, _ :_mapEmpty] := data`

The Same and Not Same Patterns

Non-associative.

SamePatt

== prim

NotSamePatt

!= prim

Same patterns match objects that compare same to their value.

>>> def state := "night"
...
... switch (state) {
...     match =="day" {"night"}
...     match =="night" {"day"}
... }
"day"

Not-same patterns match objects that do not compare same to their value:

.. todo:: test "bigMoney" =~ !="bankrupt"

Exact Pattern Expansion

>>> m`def ==specimen := value`.expand()
m`def via (_matchSame.run(specimen)) _ := value`

>>> m`def !=specimen := value`.expand()
m`def via (_matchSame.different(specimen)) _ := value`

The Quasi-Literal Pattern

Non-associative.

QuasiliteralPatt

IDENTIFIER ` QUASI_TEXT AT_IDENT @{ pattern } `

Quasiliteral patterns invoke a quasiparser with text containing pattern holes. The resulting matcher object is invoked with the object to be matched, and the patterns in the holes are matched against the specimens it extracts:

>>> "The cat and the hat." =~ `The cat and the @what.`
true

>>> "The cat and the hat." =~ `The cat and the @{what :Str}.`; what
"hat"

>>> "The cat and the hat." =~ `The cat and the @{what :Int}.`
false

Quasi-Literal Pattern Expansion

>>> m`def ``quasi @@patt`` := value`.expand()
m`def via (_quasiMatcher.run(::"````".matchMaker(_makeList.run("quasi ", ::"````".patternHole(0), "")), _makeList.run())) [patt] := value`

The via Pattern

ViaPatt

via ( expr ) pattern

Via patterns contain a view (sometimes called a transformation) and a subpattern. The view is an expression which takes a specimen and ejector and returns a transformed specimen on success or ejects on failure. This is similar to a guard but permits much richer transformations in addition to simple tests:

>>> def via (_splitList.run(1)) [x, xs] := [1, 2, 3]
... [x, xs]
[1, [2, 3]]

Final Pattern (kernel)

FinalPatt

name guardOpt

Final patterns match an object and bind a name to them, optionally testing them for guard conformance. One of the most ubiquitous patterns. Binds a name unconditionally to a final slot, which prohibits reassignment:

>>> def x := 1
... x
1

Again, any string can be used as an identifier:

>>> def ::"hello, world" := [1, 2]
... ::"hello, world"
[1, 2]

The var Pattern (kernel)

VarPatt

var name guardOpt

Var patterns match an object and bind a mutable name to them, optionally testing them for guard conformance. Guard conformance failure causes pattern match failure. Later assignments to ‘x’ will be tested for guard conformance as well.

var name := value
var name :Guard := value

Like a final pattern, but with VarSlot as the slot, which permits reassignment to the name later on using an assign expression.

Note

While var can be used to introduce a var pattern, the overall expression is still a def expression, and it can alternatively be expressed as:

def var name := value

This is useful for nesting var patterns within other patterns:

def [first, var second] := value

Bind Pattern

BindPatt

bind name guardOpt

Bind patterns match an object and bind it to a forward-declared name, optionally testing for guard conformance.

bind x
bind x ::"hello, world"
bind x :G

Expansion

>>> m`def bind x := 2`.expand()
m`def via (_bind.run(x_Resolver, null)) _ := 2`

Slot Pattern

SlotPatt

& name guardOpt

Slot patterns match an object and bind them to the slot of the pattern’s name, optionally testing the object for guard conformance.

def &name := slot

Slot Pattern Expansion

>>> m`def &x := 1`.expand()
m`def via (_slotToBinding) &&x := 1`

Binding Pattern (kernel)

BindingPatt

&& name

Binding patterns match an object and use it as the binding for the given name.

&&x
&&::"hello, world"

A bind pattern does not bind a name, but binds a binding.

def &&name := binding

Ignore Pattern (kernel)

IgnorePatt

_ guardOpt
_
_ :G

IgnorePattern matches an object, optionally requiring conformance to a guard.

def _ := value

Equivalent to value. Does nothing.

def _ :Guard := value

Performs guard coercion and discards the result.