Beginner

Control Flow in Vale

Learn conditionals, if-expressions, while loops, and foreach iteration in Vale with practical Docker-ready examples

Control flow is how a program decides what to do and how many times to do it. As an imperative systems language, Vale offers the structured control-flow constructs you would expect — if/else, while, and foreach — but with a few touches that set it apart from C++ or Rust.

Two things stand out right away. First, Vale conditions take no parentheses: you write if x < 10 { ... }, not if (x < 10). Second, the boolean connectives are spelled out as the words and, or, and not rather than &&, ||, and !. Vale also treats if as an expression — every if/else can yield a value — which removes the need for a separate ternary operator.

In this tutorial you will write conditionals, use if as an expression to compute a value, repeat work with while loops, and iterate over collections with foreach. Every example is a complete program you can compile and run with Docker.

Conditionals: if, else if, else

The if statement runs a block when its condition is true. Chain alternatives with else if, and provide a fallback with else. Conditions are combined with the keyword operators and, or, and not, and Vale’s mod operator gives the remainder (handy for divisibility tests).

Create a file named control_flow_if.vale:

import stdlib.*;

exported func main() {
  temperature = 18;

  // Basic if / else if / else chain
  if temperature < 0 {
    println("Freezing");
  } else if temperature < 15 {
    println("Cold");
  } else if temperature < 25 {
    println("Comfortable");
  } else {
    println("Hot");
  }

  // Logical operators: and, or, not
  hour = 9;
  isWeekend = false;
  if hour >= 9 and hour < 17 and not isWeekend {
    println("Working hours");
  }

  // The mod operator tests divisibility
  number = 12;
  if number mod 2 == 0 {
    println("12 is even");
  } else {
    println("12 is odd");
  }
}

temperature is 18, so the first two conditions fail and the temperature < 25 branch wins. The working-hours check passes because all three sub-conditions hold, and 12 mod 2 is 0, so the number is reported as even.

if as an Expression

In Vale, if is not just a statement — it is an expression that evaluates to the value of whichever block runs. The final expression in a block (written without a trailing semicolon) becomes that block’s value. This is how Vale does what other languages express with a ternary ? : operator.

Create a file named control_flow_expr.vale:

import stdlib.*;

exported func main() {
  n = 7;
  m = 12;

  // if is an expression: the chosen block's final value is assigned
  larger =
    if n > m {
      n
    } else {
      m
    };
  println("The larger value is: " + larger);

  // A block can do work before yielding its final value
  label =
    if n mod 2 == 0 {
      "even"
    } else {
      "odd"
    };
  println(n + " is " + label);
}

Because n (7) is not greater than m (12), the else branch yields 12, which is bound to larger. The second if-expression yields the string "odd" since 7 mod 2 is 1. Notice the values inside the branches — n, m, "even", "odd" — have no semicolon: that is what marks them as the block’s result.

Repeating Work: the while Loop

A while loop repeats its body as long as the condition stays true. Local variables in Vale are reassigned with the set keyword — plain assignment (=) introduces a new binding, while set updates an existing one. Forgetting set is the most common beginner mistake when writing loops.

Create a file named control_flow_while.vale:

import stdlib.*;

exported func main() {
  // Count from 1 up to 5
  i = 1;
  while i <= 5 {
    println("Count: " + i);
    set i = i + 1;
  }

  // Accumulate a running total of 1 through 10
  total = 0;
  n = 1;
  while n <= 10 {
    set total = total + n;
    set n = n + 1;
  }
  println("Sum of 1..10 is: " + total);
}

The first loop prints Count: 1 through Count: 5, advancing i with set i = i + 1 each pass. The second loop adds each value of n into total; after n passes 10 the loop ends and the sum, 55, is printed. Without the set n = n + 1 line the condition would never change and the loop would run forever — every while body must make progress toward its exit condition.

Iterating Over Collections: foreach

When you need to walk through a collection rather than count, use foreach. Vale’s foreach iterates over an array, and you can either take each element directly or use .entries() to get an index/value pair. The & borrows the array for reading without taking ownership of it.

Create a file named control_flow_foreach.vale:

import stdlib.*;

exported func main() {
  // Build a 5-element array of even numbers: [0, 2, 4, 6, 8]
  // The initializer {_ * 2} receives each index as `_`
  evens = [](5, {_ * 2});

  // Iterate over the elements by reference
  foreach value in &evens {
    println("Value: " + value);
  }

  // Iterate with both the index and the value
  foreach [ix, value] in evens.entries() {
    println("Index " + ix + " holds " + value);
  }
}

The array constructor [](5, {_ * 2}) builds five elements by calling the closure {_ * 2} with each index 0..4, producing [0, 2, 4, 6, 8]. The first foreach prints each value; the second destructures [ix, value] from .entries() to print both the position and the contents.

Putting It Together: FizzBuzz

FizzBuzz combines a while loop with an if/else if/else chain — a compact exercise in control flow. Order matters: the divisible-by-15 case must be checked first, otherwise a multiple of 15 would be caught by the mod 3 branch before the combined case is ever reached.

Create a file named control_flow_fizzbuzz.vale:

import stdlib.*;

exported func main() {
  i = 1;
  while i <= 15 {
    if i mod 15 == 0 {
      println("FizzBuzz");
    } else if i mod 3 == 0 {
      println("Fizz");
    } else if i mod 5 == 0 {
      println("Buzz");
    } else {
      println(i);
    }
    set i = i + 1;
  }
}

For each number from 1 to 15, the program prints Fizz for multiples of 3, Buzz for multiples of 5, FizzBuzz for multiples of both, and the number itself otherwise.

Running with Docker

These examples use the custom codearchaeology/vale:0.2 image, which bundles the Vale compiler (valec), a JDK for the Scala frontend, and clang for linking. Pull the image once, then compile and run each file. Each valec build produces a fresh ./build/main binary.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Pull the image (only needed once)
docker pull codearchaeology/vale:0.2

# Conditionals
docker run --rm -v $(pwd):/app -w /app codearchaeology/vale:0.2 bash -c '/opt/vale/valec build mymod=control_flow_if.vale --output_dir build && ./build/main'

# if as an expression
docker run --rm -v $(pwd):/app -w /app codearchaeology/vale:0.2 bash -c '/opt/vale/valec build mymod=control_flow_expr.vale --output_dir build && ./build/main'

# while loop
docker run --rm -v $(pwd):/app -w /app codearchaeology/vale:0.2 bash -c '/opt/vale/valec build mymod=control_flow_while.vale --output_dir build && ./build/main'

# foreach iteration
docker run --rm -v $(pwd):/app -w /app codearchaeology/vale:0.2 bash -c '/opt/vale/valec build mymod=control_flow_foreach.vale --output_dir build && ./build/main'

# FizzBuzz
docker run --rm -v $(pwd):/app -w /app codearchaeology/vale:0.2 bash -c '/opt/vale/valec build mymod=control_flow_fizzbuzz.vale --output_dir build && ./build/main'

Note: valec prints verbose build progress (frontend, backend, and linker stages) to stdout before your program runs. Your program’s output appears at the end; any compiler warnings from Vale’s built-in C support files are harmless.

Expected Output

control_flow_if.vale:

Comfortable
Working hours
12 is even

control_flow_expr.vale:

The larger value is: 12
7 is odd

control_flow_while.vale:

Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Sum of 1..10 is: 55

control_flow_foreach.vale:

Value: 0
Value: 2
Value: 4
Value: 6
Value: 8
Index 0 holds 0
Index 1 holds 2
Index 2 holds 4
Index 3 holds 6
Index 4 holds 8

control_flow_fizzbuzz.vale:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz

Key Concepts

  • No parentheses on conditions — Vale writes if x < 10 { ... } and while x < 10 { ... }, omitting the parentheses that C++ and Rust require around the condition.
  • Word operators — boolean logic uses the keywords and, or, and not instead of &&, ||, and !. The mod operator returns the remainder of integer division.
  • if is an expression — every if/else evaluates to a value, so you assign the result directly (grade = if ... { ... } else { ... };) instead of reaching for a ternary operator.
  • Blocks yield their last expression — the final expression in a block, written without a trailing semicolon, becomes the block’s value; everything before it runs as ordinary statements.
  • = declares, set reassigns — a plain = introduces a new binding, while updating an existing variable inside a loop requires set. Mixing these up is the most common loop bug in Vale.
  • while for counting, foreach for collections — use while with a set-updated counter for indefinite repetition, and foreach value in &collection (or .entries() for index/value pairs) to walk arrays.
  • Order your conditions deliberately — in an else if chain only the first matching branch runs, so place the most specific case (like FizzBuzz’s divisible-by-15 test) before the more general ones.

Running Today

All examples can be run using Docker:

docker pull codearchaeology/vale:0.2
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining