Control Flow
J2 spells its control flow in plain words: repeat while a
condition holds, stop to leave a loop, skip to move on, and
until to bound an iteration.
Conditionals
if takes any value as its condition and applies the truthiness rules
from Values and Types. Arms chain with else if and
close with an optional else. Braces are always required.
score = 87
if score >= 90 {
print("A")
} else if score >= 80 {
print("B")
} else {
print("C")
}
Iteration: for
for x in ... walks an iterable: an inclusive range, a sequence, or a
flow. Text is not iterable directly; walk its characters with
text.chars.
for i in 1..3 { print(i) } # 1 2 3
for name in ["ada", "grace"] { print(upper(name)) }
for ch in text.chars("j2") { print(ch) } # j then 2
Use _ as the loop variable when the body does not need it:
for _ in 1..3 { print("tick") }
Destructuring pairs
When the elements are pairs, two loop variables receive the halves. This is how
enumerate and zip are consumed:
for i, word in enumerate(["fig", "kiwi"]) {
print(fmt("{}: {}", i, word)) # 0: fig, then 1: kiwi
}
for a, b in zip([1, 2], [30, 40]) {
print(a + b) # 31 then 42
}
Filtering with if
A trailing if runs the body only for elements that pass. The filter is
either an expression over the loop variable or the bare name of a one-argument function,
which is applied to each element automatically:
for i in 1..10 if i % 2 == 0 { print(i) } # 2 4 6 8 10
func is_short(w) = len(w) < 5
for w in ["fig", "banana", "kiwi"] if is_short { print(w) } # fig kiwi
Bounding with until
A trailing until ends the loop as soon as its condition becomes true.
The condition is checked after the body runs, so the triggering element is processed
before the loop ends:
total := 0
for x in [5, 2, 8, 1] until x > 7 {
total += x
}
print(total) # 15: processes 5, 2, and 8, then stops
until is also the idiomatic way to consume an infinite flow such as
1..; see Sequences, Maps, and Flows.
Iteration: repeat, do, loop
repeat is the while loop, spelled the way you would say it:
n := 1
repeat n < 100 {
n *= 2
}
print(n) # 128
do ... repeat checks its condition after each pass, so the body always
runs at least once:
attempts := 0
do {
attempts++
} repeat attempts < 3
print(attempts) # 3
loop runs forever until something ends it:
z := 0
loop {
z++
stop if z >= 5
}
print(z) # 5
stop and skip
Inside any loop, stop exits the loop and skip abandons the
current iteration. Both come in a bare form and a conditional form, and the conditional
form usually reads better than wrapping an if around it:
shouting := 0
for w in ["hey", "STOP", "more"] {
skip if len(w) < 4
shouting += 1
}
print(shouting) # 1
acc := 0
for i in 1..100 {
stop if i * i > 20
acc += i
}
print(acc) # 10: 1 + 2 + 3 + 4
stop leaves one loop. To leave several nested loops at once, put the
loops in a function and give the answer; give unwinds the
whole function immediately (see Functions).
Assertions
assert checks a condition and ends the program with a
RuntimeError when it fails. An optional message after a comma names what
went wrong. Asserts document intent as much as they check it, and they run in both
execution engines.
func mean(xs) = sum(xs) / len(xs)
data = [4, 8, 6]
assert len(data) > 0, "mean needs a non-empty seq"
print(mean(data)) # 6
The related builtin assert_eq(a, b) fails unless its two arguments are
equal, and reports both values when they are not.