Contents

Searching...

Control Flow

Sometimes you gotta branch out...

It's not necessary that all of your code needs to run sequentially. Sometimes, you need to run a piece of code conditionally. Control Flow statements allow you to achieve this. Let's start with the most basic control flow statement:
if
.

The
if
block executes only if the given condition is true. The condition is a boolean expression. The
if
block starts with an opening brace (
{
) and ends with a closing brace (
}
).
var name = "Kal".
if name == "Kal" { stdout "Yes\n". }

if.kal

Meanwhile,
else
block is executed when the condition for the
if
block fails.
if 2 + 2 == 5 {
    stdout "Yes\n".
}
else { stdout "No\n". }

ifElse.kal

else
block is similar to if except it does not accept a boolean condition. It keeps track of the condition associated with the if block and executes
if
the condition evaluates to false.

else
blocks are not mandatory. You can have
if
blocks without else blocks.

Now, here's an interesting addition, Kal allows you to split
if
and
else
blocks with other statements in between without any problems!
var name = "Kal".
;; block for conditon A if 2 + 2 == 5 { stdout "Hello\n". }
;; block for condition B if name == "Kal" { stdout "Hi\n". }
;; block for condition B else { stdout "Hey\n". }
;; block for condition A else { stdout "World\n". }

interstingIfElse.kal

Kal smartly keeps track of which
else
block is associated with what
if
block.
if
and
else
blocks can be nested together.
var name = "Kal".
if name == "Kal" { var sum = 2, line = "Awesome".
if 1 + 1 == sum { stdout line "\n". } }

nestedIf.kal

You can have more conditional branch apart from
if
and
else
. Use
elif
in that case.
var color = "yellow".
if color == "red" { stdout "Stop!\n". } elif color == "yellow" { stdout "Get Ready!\n". } else { stdout "Go!\n". }

elif.kal

Sometimes you may need to declare and use a variable only for a specific context. Take this for an example:
var age = 22.
if age > 18 { stdout "You are " age "...\n". stdout "You can drive!\n". }

verifyAge.kal

In this case, the
age
variable was declared before the
if
block and used only within it. This can be compacted into more context aware boolean conditions using the Walrus operator. The Walrus Operator (
:=
) allows you to declare variables as part of an expression.
var y = (x := 10) * 2.
stdout "X: " x " Y: " y "\n".

walrus.kal

Here
x
is declared and set to 10 within the expression and the assigned value is utilized to evaluate for
y
.

Using walrus operator, the previous
verifyAge.kal
program can be compacted to:
if (age := 22) > 18 {
    stdout "You are " age "...\n".
    stdout "You can drive!\n".
}

verifyAge.kal

In Boolean Algebra, operators
&&
and
||
exhibit an interesting property. Take a look at this table:

XY&&||
FalseFalseFalseFalse
FalseTrueFalseTrue
TrueFalseFalseTrue
TrueTrueTrueTrue


Here you can observe that in 50% of the cases:
  • For
    &&
    , the final result is false if the first operand is false.
  • For
    ||
    , the final result is true if the first operand is true.
In such a case, the second operand is not required to be evaluated. This is called Shortcircuiting.

This may sound insignificant but boolean shortcircuiting can save a huge overhead if the second operand involves heavy computation.

Consider the following example:
fn x -> {
    <- 0.
}
fn y -> { <- 1. }
if $(:x) && $(:y) { stdout "Done\n". }

shortAnd.kal

Function
x
is invoked since it's the first operand, and the stdout statement is executed. Since its return value is 0 and the expression evaluates && operation to 0, the invocation of function
y
is entirely skipped.

Similar behavior can be seen with
||
.
fn x -> {
    stdout "Invoked X\n".
    <- 0.
}
fn y -> { stdout "Invoked Y\n". <- 1. }
if $(:y) || $(:x) { stdout "Done\n". }

shortOr.kal

Here, invocations for function
x
and
y
have swapped places so that the function returning 1 is invoked first, making the entire expression to result in 1 instantly without invoking function
x
.

You may need both of the functions to be invoked regardless, especially if the functions produce useful side effects. In that case, you can invoke the functions first and use their return values in an expression later.
fn x -> {
    stdout "Invoked X\n".
    <- 0.
}
fn y -> { stdout "Invoked Y\n". <- 1. }
:y -> yVal. :x -> xVal.
if yVal || xVal { stdout "Done\n". }

invokeAll.kal

Conditionally executing your code is not everything. You also need to execute code multiple times. That's where
loop
comes in. A loop executes a block of code repeatedly based on the boolean condition provided to it. The conditions can be provided to it in many ways. Take for example:
var count = 0.
loop count < 5 { stdout "Hello\n". count = count + 1. }

loop.kal

Analogous to the while loop, if loop takes in only one segment (boolean condition), that means the loop keeps on repeating till the condition evaluates to false.

You can also use other control flow constructs in
loop
. Take
if
for example:
var count = 1.
loop count <= 5 { if count % 2 == 0 { stdout "Hello x" count "\n". } count = count + 1. }

loop.kal

A much compact loop can be written with the declaration, condition and increment in a single line, much analogous to a
for
loop.
loop count = 1 -- count < 5 -- count = count + 1 {
    stdout "Hello\n".
}

loop.kal

Notice that the segments are separated by
--
.

You can write each segment on a new line for better readability.
loop count = 1 --
     count < 5 --
     count = count + 1 {
     stdout "Hello\n".
}

loop.kal

Not all segments are mandatory in
loop
. You can skip them if you have provisioned them somewhere around or inside the loop block.

Take these for examples:
;; skipping condition segment
loop count = 1 -- -- count = count + 1 { if count == 6 { break. }
stdout "Count: " count "\n". }

loop101.kal

;; skipping increment segment
loop count = 1 -- count <= 5 { stdout "Count: " count "\n". count = count + 1. }

loop110.kal

;; skipping condition and increment segment
loop count = 1 -- -- { if count == 6 { break. }
stdout "Count: " count "\n". count = count + 1. }

loop100.kal

;; skipping assignment and condition segment
var count = 1.
loop -- -- count = count + 1 { if count == 6 { break. }
stdout "Count: " count "\n". }

loop001.kal

Notice how some of the examples use
break
. It's also not mandatory that you run a loop the number of times it's designed to run. You can break out of a loop before it finishes all its iterations.

Consider this program which breaks out of the loop once it hits an even number in the list it's iterating on.
var data = [1, 3, 5, 8, 10],
    itr = 0.
loop itr < $(len data) { if data[itr] % 2 == 0 { break. } stdout data[itr] "\n". itr = itr + 1. }

break.kal

Breaking out of a loop is a hard stop. You may want to skip loop iterations sometimes.
continue
does that for you.
loop count = 1 -- count <= 10 -- count = count + 1 {
    if count == 4 || count == 9 {
        continue.
    }
stdout count " ". }
stdout "\n".

continue.kal

Notice that only 4th and 9th iterations are skipped while the loop body executes as expected for other iterations.

One of the most useful constructs of programming is a nested loop. A nested loop is a loop body that executes inside another loop body. Loops can be nested to any number of levels.
loop i = 1 -- i <= 5 -- i = i + 1 {
    loop j = 1 -- j <= 5 -- j = j + 1 {
        if i == j {
            continue.
        }
        stdout "(" i ", " j ")\n".
    }
}

nestedPairs.kal



This example demonstrates a nested loop. For every iteration of the outer loop, the inner loop runs multiple times. In this case continue is used to skip an iteration where the values of
i
and
j
are the same. The program results in pairs of numbers 1 to 5 without a repeating number in a pair.

Perhaps a very well known sorting algorithm that uses nested loops may help...
var data = [3, 1, 5, 4, 2].
stdout "Before: " data "\n".
var i = 0. loop i < $(len data) { var j = i. loop j < $(len data) { if data[i] > data[j] { ;; swap values var [data[i], data[j]] = [data[j], data[i]]. } j = j + 1. } i = i + 1. }
stdout "After: " data "\n".

bubbleSort.kal

You can cleanly loop over a list via a ranged based loop. A range based loop takes care of that starting point, ending point and the increment by itself.
var data = [1, 2, 3].
loop each in data { stdout each "\n". }

rangeLoop.kal

In this case the variable
each
holds the value of the each element in the data list during its respective iteration.

Modifying a list element inside a range based loop body, does not modify the original value, it only modifies the temporary value.

Range based loops can also be nested.
var data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]].
loop x in data { loop y in x { stdout y " ". }
stdout "\n" }

nestedRange.kal

You can use the list unpacking syntax to unpack complex data in the loop segment itself.
var data = [[1, 2, 3], [4, 5, 6], [7, 8, 9]].
loop [x, y, z] in data { stdout x " " y " " z "\n". }

unpackLoop.kal