Statements and Control Flow
Klyn uses indentation-defined blocks and a compact statement vocabulary. This page covers the control-flow statements you will use constantly in real code.
count = 10
name as String = "Ada"
count += 1
count--
Assignment updates the nearest visible variable while preserving its established type. If no visible variable has that name, the assignment declares it in the current lexical block. Such a declaration is not visible after that block ends.
A nested scope cannot declare another local variable with the same name as a visible local or parameter. This includes explicit local declarations, loop declarations, catch variables and lambda parameters. Reusing a name in separate sibling scopes is allowed. Assignment to an existing variable remains legal: it updates that variable rather than declaring a second one.
Comprehensions reuse visible variables instead of declaring a second variable with the same name.
Only iteration names that do not already exist are local to the comprehension.
For example, after x = 100 and values = [x * 2 for x in [1, 2]],
x is 2 and values is [2, 4].
score = 72
if score >= 90:
print("excellent")
elif score >= 50:
print("pass")
else:
print("retry")
Every branch owns a lexical scope. A variable declared before the conditional can be updated
by any branch; a variable first declared in an if, elif, or
else branch remains local to that branch.
Conditions are strictly typed as Boolean. Klyn does not apply truthiness to numbers,
strings, collections, objects, or null; the same rule applies to while,
assert, conditional expressions, Boolean operators, and comprehension filters.
score = 72
label = "unknown"
if score >= 50:
label = "pass"
congratulation = "well done"
else:
label = "retry"
print(label)
# print(congratulation) # TypeError: the name belongs to the if branch.
value = 2
match value:
case 0:
print("zero")
case 1 | 2:
print("small")
case 3 to 10:
print("range")
default:
print("other")
match compares one subject against ordered cases. A case can use an exact value,
several alternatives separated by |, or an inclusive interval with to.
default handles the fallback branch.
The compiler rejects case nan and case nanf with a
SyntaxError. IEEE 754 defines every equality comparison involving NaN, including
NaN with itself, as false, so these constant patterns could never match. Test a value with
is nan or is nanf before the match instead.
text = "dominique@example.com"
match text:
case /^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z]{2,}$/:
print("email")
case /^[0-9]{2}-[0-9]{2}-[0-9]{4}$/:
print("date")
default:
print("unknown")
Regex cases use the same literal syntax as regular expressions and test the subject with pattern matching.
command = "ls"
action = match command:
case "ls" | "dir":
"listing"
case "ps":
"processes"
default:
"unknown"
A match expression returns the expression produced by the first matching branch.
Use this form when a value must remain available after the match. Variables first
declared inside a case or default block remain local to that branch.
value = 1
result = 0
match value:
case 0:
result = 10
explanation = "zero"
case 1:
result = 20
default:
result = 30
print(result)
# print(explanation) # TypeError: explanation belongs to one case only.
i = 0
while i < 3:
print(i)
i += 1
Use while when the continuation condition depends on values updated in the body.
Variables declared before the loop can be updated by it; names first declared in the body are
local to the body.
Klyn has no separate do or until loop form. When a body must execute
before its termination test, use while true with an explicit conditional
break.
for i = 0 to 5:
print(i)
Both bounds are inclusive. The example prints 0 through 5.
The binding i exists only in the loop body. When iterating over indices, use an
explicit upper bound such as values.size - 1.
for value in [10, 20, 30]:
print(value)
config = {"theme": "dark", "fullscreen": true}
for key in config:
print(key)
for key, value in config:
print(key + " = " + value)
records = [
Tuple(1, "ready", true),
Tuple(2, "waiting", false)
]
for identifier as Int, label as String, enabled as Boolean in records:
print(f"{identifier}: {label} ({enabled})")
Iterating directly over a map yields keys. Use keys(), values(), or
items() when you want a specific view, or provide two bindings to destructure its
key and value directly. An iterable of positional tuples can be destructured into any number of
bindings; the number of bindings must equal the tuple arity. Each iteration binding is local to
its loop and cannot be read after that loop ends.
while true:
text = input("> ")
if text == "":
continue
if text == "quit":
break
print(text)
count = 1
featurePending = true
assert count > 0
if featurePending:
pass
assert is appropriate for invariants and tests. pass is an explicit
placeholder that leaves a block intentionally empty.