Skip to content

Loops and Flow Control

Conditional Branches

if else expressions are expressions in Gambol meaning that they can be evaluated to a value. For example:

x = 12
y = 100 if x < 12 else 3.5

The above shows the syntax for inline conditionals. The type a conditional is evaluated to is determined by the type of the first branch that is not disabled (more on disabled branches later). In the above example the inferred type for y is going to be NInt even if the second branch of the conditional returns a floating point value.

Here is an example for conditionals with multiple branches:

if x == 12 then

    print(`x == 12`)

else if x < 5 then

    print(`x < 5`)

else  

    print(`x > 20`)

end

The conditional expression must be assignable to Bool.

Lexical Scope

The branches in if-else expressions do not open a new scope. If you like though you could add a scope like so:

if x then {
    print(`this is a new scope`)
} else {
    print(`another scope`)
}
end

While Loops

Loops are statements and do not evaluate to any value. While Loops can be written both with condition at the top and at the bottom. Like conditinal branches they do not introduce a new scope.

x = 0
do
    x += 1
while x < 10

while x < 20 do

    x += 1
end

Loop Control

To break out of a loop use the break keyword and to skip to the next iteration use continue:

x = 0

while x < 20 do

    print(x)
    if x > 5 then break end

    x += 1
end

In case there are nested loops you can specify which loop to continue or break out of by adding the loop index to the break or continue statements.

x = 0

while true do #! index 2
  while true do #! index 1
    while true do #! index 0

      x += 1

      if x > 4 then break 2 end

    end
  end
end

print(x) #! prints 5

The index for the current loop is 0 and increments towards the most outward loop.

For Loops

A for loop is a syntactic sugar that ultimately gets reduced to a while loop by the compiler. Unlike while loops, for loops introduce a lexical scope which is also reflected in the syntax. There are multiple ways the compile will reduce a for loop which are enumerated below:

Notice the induction variable in all examples below is defined within the lexical scope.

  • Use with containers

    l = [1,2,3,4,5]
    
    for x in l {
        print(x)
    }
    
    The above for loop will get roughly translated to:

    {
      iter auto = l.get_iterator()
      while iter.has_next() do
        try
    
          x auto = iter.next()
          print(x)
    
        catch e IteratorStopException then break end
      end
    }
    
  • Use with an IndexRange

    for x in 1..5 {
        print(x)
    }
    

    Just like a container IndexRange can be iterated over. However the compiler might perform additional optimizations as to what the loop above will ultimately get reduced to so it may not call the get_iterator of IndexRange at all. For example the above might get translated to:

    {
      x auto = 1
      while x < 5 do
          print(x)
          x += 1
      end
    }
    
  • Unrolling at compile time

    #unroll
    for x in 1..5 {
        print(x)
    }
    

    Unrolling is useful in situations where performance is critical and a jump instruction cannot be afforded or in situations where the loop induction variable (e.g. x above) needs to have a compile-time value. Unroll can only be used with IndexRanges that have compile time evaluation. The above IndexRange 1..5 can be evaluated at compile time obviously and the entire loop will be translated to:

    {
        print(x)
        print(x)
        print(x)
        print(x)
    }
    

Variable Type

You can explicitly set the loop variable type like so

l = [1,2,3,4,5]

for x Int32 in l {
    print(x)
}

The elements in l above will get assigned to Int32. You may also take the list elements by reference to modify them in place if the iterator actually returns them by reference:

l = [1,2,3,4,5] List[Int64]

for x @auto in l { x *= 2 }

print(l) #! prints [2, 4, 6, 8, 10] 

For iterators that return multiple values you can define multiple variables or skip over some of them:

dict = [`name`: `John`, `age`: 30, `height`: 6.4]

for k, v in dict {
    print(`key: ` k `, value: ` v)
}

output:

key: name, value: John
key: age, value: 30
key: height, value: 6.40000000000000035527

To skip the value:

for k, in dict {
    print(k)
}

output:

name
age
height

Zip multiple iterables

Using for loops you can iterate over multiple collections or iterables. Whichever iterator is exhausted first determines the final length of the loop:

keys = [`name`, `age`, `height`]
values = [`james`, 13, 33, 5784, `hi`, 3.3, NaN]

for k, v in keys, values {
    print(`key: ` k `, value: ` v)
}

output:

key: name, value: james
key: age, value: 13
key: height, value: 33

This could be useful if you want to enumerate over the elements of a list or dictionary as in this example:

dict = [`name`: `John`, `age`: 30, `height`: 6.4]

for k, v, i in dict, ..dict.len() {
    print(`` i ` - key: ` k `, value: ` v)
}

output

0 - key: name, value: John
1 - key: age, value: 30
2 - key: height, value: 6.40000000000000035527