Pattern matching
match is the only way to look inside an enum, and it doubles as HollowScript’s general-purpose branching-on-shape tool for records, lists and literal values. There are no guards and no if let, a pattern either matches or it does not.
uses print
record Point = { x: Int, y: Int }
fn describe(n: Int): String { match n { 0 { return "zero" } 1, -1 { return "one, either sign" } else { return "something else" } }}
fn where(p: Point): String { match p { { x: 0, y: 0 } { return "origin" } { x: 0 } { return "on the y axis" } { y: 0 } { return "on the x axis" } { x, y } { return "at ${x}, ${y}" } }}
fn summary(xs: [Int]): String { match xs { [] { return "empty" } [only] { return "one: ${only}" } [first, ..rest] { return "${first} then ${rest.length()} more" } }}
effect print(describe(-1))
effect print(where({ x: 0, y: 5 }))
effect print(where({ x: 2, y: 3 }))
effect print(summary([]))
effect print(summary([7]))
effect print(summary([1, 2, 3]))Output:
one, either signon the y axisat 2, 3emptyone: 71 then 2 moreA match arm can list several patterns separated by commas (1, -1), matching if any of them does. Patterns are tried top to bottom, and the first one that matches wins.
Exhaustiveness. Every match must cover every possible value of the type being matched, or say else. Leaving a case out is a compile-time error, not a runtime surprise, checked by the same algorithm whatever the shape of the type: an enum needs an arm per variant, Bool needs true and false, and anything with unboundedly many values (Int, String, lists, and so on) needs a catch-all. See section 7.5 and section 7.7.
Telling variants and bindings apart. A pattern that starts with an upper-case letter names an enum variant (Circle, Ok); one that starts lower-case binds a new name instead. _ is its own pattern form: it matches anything and binds nothing at all, so it can never be read back as a value. This lets a pattern be read correctly without already knowing the type it is checked against. See section 7.2 and section 7.3.
Destructuring
Section titled “Destructuring”A constant or variable declaration, and the variable of a for loop, can bind names straight out of a record or a list, using the same pattern syntax minus anything that could fail (no literals, no none, no variant patterns, those need match instead, because a declaration has nowhere to send a failure):
uses print
record Point = { x: Int, y: Int }
record Segment = { start: Point, end: Point }
constant s: Segment = { start: { x: 1, y: 2 }, end: { x: 4, y: 6 } }
constant { start: { x: x1, y: y1 }, end: { x: x2, y: y2 } } = s
constant [first, second, ..rest] = [10, 20, 30, 40]
effect print("${x2 - x1} ${y2 - y1} ${first + second} ${rest.length()}")Output:
3 4 30 2A plain list pattern only matches a list of exactly that length (one with a rest pattern, [first, ..rest], matches a list with at least as many elements as it has patterns before the rest); constant [a, b] = someList faults at run time if someList does not have exactly two elements. See section 7.8.