07 - Control Flow
Control flow in Verge is deliberately unsurprising. The whole point of control flow is to
make a program’s behaviour less surprising, not more, so nothing here should be
unexpected — with the possible exception of match, which is more capable than its
equivalent in many languages.
if / else if / else
Conditions don’t need parentheses, and the branches are braced blocks:
import std.io
classify: f(n: i32) => string {
if n > 0 {
return "positive"
} else if n == 0 {
return "zero"
} else {
return "negative"
}
}
main: f() {
io.writeln(classify(5))
io.writeln(classify(0))
io.writeln(classify(-3))
}
positive
zero
negative
Chain as many else if branches as you like; a bare else is the fallthrough.
while
while runs its body as long as the condition holds:
import std.io
main: f() {
count := 3
while count > 0 {
io.writeln("tick")
count--
}
}
tick
tick
tick
for over collections
Iterating a slice or array binds each element in turn with for value := collection:
import std.io
main: f() {
items := []string{"tea", "coffee", "water"}
for item := items {
io.writeln(item)
}
}
tea
coffee
water
When you want the index too, bind a pair. The parentheses are optional — for i, item :=
and for (i, item) := are the same:
import std.io
main: f() {
items := []string{"tea", "coffee", "water"}
for i, item := items {
io.writeln("{i}: {item}")
}
}
0: tea
1: coffee
2: water
Maps iterate the same way, binding key, value:
import std.io
main: f() {
prices := map.new<string, i32>()
prices["tea"] = 3
prices["coffee"] = 4
for name, price := prices {
io.writeln("{name} costs {price}")
}
}
tea costs 3
coffee costs 4
(Map iteration order is unspecified, so don’t rely on it.)
Range loops
For a plain counter, iterate a numeric range. a..b is half-open (excludes b); a..=b is
inclusive:
import std.io
main: f() {
for i := 0..3 {
io.write("{i} ")
}
io.writeln("")
for i := 0..=3 {
io.write("{i} ")
}
io.writeln("")
}
0 1 2
0 1 2 3
C-style for
When you need full control over the counter — a custom step, multiple conditions — the three-clause C-style form is there too:
import std.io
main: f() {
for i := 0; i < 10; i += 2 {
io.write("{i} ")
}
io.writeln("")
}
0 2 4 6 8
break, continue, and return
The usual escape hatches: continue skips to the next iteration, break leaves the loop,
and return leaves the whole function:
import std.io
first_even: f(values: []i32) => i32 {
for value := values {
if value < 0 {
continue # ignore negatives
}
if value % 2 == 0 {
return value # found one, leave the function
}
}
return -1
}
main: f() {
io.writeln(first_even([]i32{-4, 3, 7, 8, 10}))
}
8
break looks the way you’d expect:
import std.io
main: f() {
for i := 0..10 {
if i == 4 {
break
}
io.write("{i} ")
}
io.writeln("")
}
0 1 2 3
Pattern matching with match
match is where Verge pulls ahead of a stack of if statements. It compares a value against
a series of patterns and evaluates the first arm that fits. A match is an expression — it
produces a value — so it can be a function’s whole body:
Matching enums
Enum arms can destructure a variant’s payload right in the pattern:
import std.io
Color: enum {
Red
Green: i32
Custom: object {
R: u8
G: u8
B: u8
}
}
describe: f(color: Color) => string {
return match color {
Red => "red"
Green(shade) => "green (shade {shade})"
Custom(rgb) => "rgb({rgb.R}, {rgb.G}, {rgb.B})"
}
}
main: f() {
io.writeln(describe(Color.Red))
io.writeln(describe(Color.Green(7)))
io.writeln(describe(Color.Custom(R: 255, G: 128, B: 64)))
}
red
green (shade 7)
rgb(255, 128, 64)
Matching integers and ranges
Arms can be literals, ranges, or the _ catch-all. Here the match is the function body
directly, with no return:
import std.io
bucket: f(score: i32) => string {
match score {
0 => "zero"
1..=10 => "low"
11..=50 => "medium"
_ => "high"
}
}
main: f() {
io.writeln(bucket(0), " ", bucket(7), " ", bucket(40), " ", bucket(99))
}
zero low medium high
Matching strings
import std.io
route: f(path: string) => string = match path {
"/" => "home"
"/about" => "about"
_ => "404"
}
main: f() {
io.writeln(route("/"), " | ", route("/about"), " | ", route("/nope"))
}
home | about | 404
_ is the catch-all arm — the language’s polite way of saying “everything else, I guess”.
The compiler checks that a match covers every case, so leaving a gap is a compile error
rather than a lurking bug.
That’s the everyday toolkit. If you can read if, while, for, and match, you can read
the overwhelming majority of real Verge code.