Skip to content
Axel Faes edited this page Sep 13, 2015 · 2 revisions

Control flow is about branching and looping through chunks of code to determine what to execute next.

All control flow is based upon truth, deciding whether or not to do something. Obviously, the boolean true is in the "true" bucket and false is in "false", but what about values of other types? The choice is ultimately arbitrary, and different languages have different rules. Cardinals rules follow Ruby:

  • The boolean value false is false.
  • The null value null is false.
  • Everything else is true

If statement

We start with the simple and familiar if statement. The simplest branching statement, if lets you conditionally skip a chunk of code. It looks like this:

if (condition) oneline.code()

That evaluates the parenthesized expression after if. If it's true, then the statement after the condition is evaluated. Otherwise it is skipped. Instead of a statement, you can have a block:

if (condition) {
  IO.print("multiline")
  IO.println(" if")
}

You may also provide an else branch. It will be executed if the condition is false:

if (condition) oneline.code() else oneline.elseSide()

And, of course, it can take a block too:

if (condition) {
  IO.print("multiline")
  IO.println(" if")
} else {
  IO.print("multiline")
  IO.println(" else")
}

However the following is not allowed. It is not clear that the else belongs to the if

if (condition) {
  IO.print("multiline")
  IO.println(" if")
} 
else {
  IO.print("multiline")
  IO.println(" else")
}

While and for loops

A while statement executes a chunk of code as long as a condition continues to hold.

var a = 0
while (a < 100) {
    a = a + 1
}

A while loop can also be a one-liner without braces.

var a = 0
while (a < 100) a = a + 1

A for loop iterates through a sequence of elements.

for(a in [0,1,2,3]) {
     IO.println(a)
}

A for loop has three components:

  1. A variable name to bind. A new variable with that name whose scope is the body of the loop will be created.

  2. A sequence expression. This determines what you're looping over. It gets evaluated once before the body of the loop. In this case, it's a list literal, but it can be any expression.

  3. A body. This is a curly block or a single statement. It gets executed once for each iteration of the loop.

Break

Sometimes, right in the middle of a loop body, you decide you want to stop. To do that, you can use a break statement. That will immediately exit out of the nearest enclosing while or for loop.

``` dart
var a = 0
while (a < 100) {
    a = a + 1
    if (a > 50) break
}

Numeric range

You can create ranges in Cardinal:

for (i in 1..100) {
    IO.print(i)
}

The .. and ... are simply operators and call a method. But the number type implements them and returns a range object that knows how to iterate over a series of numbers. The 1..5 means from 1 to 5 inclusive. The 1...5 means from 1 to 5 exclusive.

Iteration

Lists and ranges cover the two most common kinds of loops, but you should also be able to define your own sequences. To enable that, the semantics of a for are defined in terms of an "iterator protocol". The loop itself doesn't know anything about lists or ranges, it just knows how to call two particular methods on the object that resulted from evaluating the sequence expression.

When you write a loop like this:

for (i in 1..100) {
    IO.print(i)
}

This will be compiled into:

var iter_ = null
var seq_ = 1..100
while (iter_ = seq_.iterate(iter_)) {
    var i = seq_.iteratorValue(iter_)
    IO.print(i)
}

Some built-in types such as maps, lists and ranges implement the iterate and iteratorValue functions. But any object using these functions can be iterated over. The Sequence class defines a couple useful functions, so any iterable object should preferably inherit from Sequence.

Clone this wiki locally