proc main(): void {
command := ["move", "north", "2"]
if command is ["move", dir, ...steps] {
echo "{dir}, {len(steps)} more"
} else if command is ["stop"] {
echo "halt"
}
}proc main(): void {
if "/users/42/posts/7" is "/users/{id}/posts/{postId}" {
echo "{id} / {postId}" // 42 / 7
}
if "a/b/c" is "{x}/{y}" {
echo "{x} | {y}" // a | b/c — the shortest first hole
}
if "/users/42/extra" is "/users/{id}" {
echo id // 42/extra — a last hole takes the rest
}
if "/users/42/extra" is "/users/{id is (\d+)}" {
echo "never: digits only, and the template covers the whole string"
}
}func route(path: Str): Str {
// Only digits reach id, so /users/nobody falls through
// to the next branch instead of matching this one.
if path is "/users/{id is (\d+)}" {
return "user #{id}"
}
// The two kinds of hole mix freely — they are one construct.
if path is "/api/v{version is (\d+)}/{rest}" {
return "v{version}, then {rest}"
}
// Braces inside the parentheses are the regex's own: this is
// four digits, not an interpolation.
if path is "/logs/{year is (\d{4})}" {
return "logs for {year}"
}
return "no match"
}
proc main(): void {
echo route("/users/42") // user #42
echo route("/users/nobody") // no match
echo route("/api/v2/things") // v2, then things
echo route("/logs/2026") // logs for 2026
}proc main(): void {
path := "/users/42"
// Each of these is refused before the program is built:
// if path is "/users/{id}{name}" { } // two open holes side by side
// if path is "/users/{len(path)}" { } // a hole binds, it does not compute
// if path is "{a is (a{2,1})}" { } // {2,1} counts down
// if path is "{a is ((?=b)c)}" { } // no lookaround
// if path is "{a is ((b)\1)}" { } // and no backreference
// Fine: the first hole says where it ends.
if path is "/users/{id is (\d+)}{rest}" {
echo "{id}[{rest}]"
}
}type Route {
Page { segments: Str[dyn] }
Missing
}
func resolve(path: Str): Result<Route, Str> {
if path == "" {
return Result.Error("empty")
}
return Result.Ok(Route.Page(split(path, ",")))
}
proc main(): void {
path := "foo/abc/bar,x,y"
// A Result holding a Page, holding a vector whose first element
// is a string with a hole in it: one pattern.
if resolve(path) is Result.Ok(Route.Page(["foo/{padding}/bar", ...rest])) {
echo "{padding}, then {len(rest)} more" // abc, then 2 more
}
// The very same match, written out as the chain it abbreviates.
if resolve(path) is Result.Ok(route) && route is Route.Page(parts) && parts is [head, ...rest] && head is "foo/{padding}/bar" {
echo "{padding}, then {len(rest)} more"
}
// A literal stands where a name would: the field has to equal it.
if resolve("") is Result.Error("empty") {
echo "nothing to resolve"
}
}A vector pattern matches element by element. An element is a literal — an Int, Float, Str, Atom or Bool — a name that binds it, or a pattern of its own, and a last ...rest binds whatever follows as a vector. Without one, the lengths have to be equal.
A string pattern is a template whose {holes} bind the text between its literal pieces. The template covers the whole string, and each hole takes the shortest text that lets the rest match — so "a/b/c" is "{x}/{y}" gives x a and y b/c, and a hole at the end takes everything left: "/users/42/extra" is "/users/{id}" binds id to 42/extra. That is the routing trap a hole that says what it takes avoids: {id is (\d+)} binds only what the regular expression matches.
Nothing between those parentheses is escaped by the string — {year is (\d{4})} is four digits, not an interpolation of 4 — and the regular expression is read at compile time, so a malformed one is a compile error naming what is wrong. A pattern has to have one reading: two open holes may not sit side by side with no text between them, though a hole that says what it takes ends itself; and a hole is a plain name, since a pattern binds and does not compute. A string being built, rather than matched, cannot hold a regex hole.
Patterns nest. Wherever a pattern binds a name — a variant's field, a vector's element — it may hold a pattern instead, and the whole is still one match: r is Result.Ok(Route.Page(["home", ...rest])). It is exactly the && chain it abbreviates, outer value first, and compiles to the same program. A variant's field may also hold a literal it has to equal. One pattern binds a name once, and a variant pattern has to be of the type it is matched against; anything else is a compile error.