Flow Control in EK9

EK9 has a range of syntax that enables a developer to control the flow of processing. These are broadly what you would expect in a general programming language and are similar to most other programming languages.
But there are a couple of additions in EK9 that mean you may not need to use these imperative (traditional) flow control statements quite as much. There are also a number of enhancements that make flow control much easier in EK9.

The Controls

In general it has been found that adopting a more functional approach to flow control leads to a decrease in developer cognitive load, simplicity, flexibility, reduction in code volume, more code reuse and fewer defects.
Linked with the new EK9 operators; traditional flow control is needed much less (but in some cases it is still required).

The examples shown below start with simple version of each flow-control syntax and then show the additional features in EK9.

The traditional flow control mechanisms available are broadly categorised as follows:

Branching
Looping (imperative/procedural)
Streaming (more functional)

Most of the above processing can also be implemented using this more functional technique.

Control examples

Examples of the different types of flow control are show below in the next sections.

If statements

A traditional control statement - but also review ternary operators and assignment coalescing. You may find that syntax much more appealing and terse in certain situations. See contrast of if/else and ternary operators for common situations and especially for dealing with variables that may be un-set.

But when you really do need an if statement EK9 has a number of additional features that give you more control and flexibility.

Simple If statements

This is the 'if/else' in its simplest form.

#!ek9
defines module introduction

  defines function

    testIfElseInteger()
      -> first as Integer

      trigger <- false

      if first > 21
        trigger: true
      else if first < 10
        trigger: false
      else
        trigger: Boolean()

      //Just ensure referenced
      assert trigger?

    testIfElseIntegerAsPure() as pure
      -> first as Integer

      trigger <- Boolean()

      if first > 21
        trigger :=? true
      else if first < 10
        trigger :=? false

      //Just ensure referenced
      assert trigger?
//EOF

The above example testIfElseInteger should be fairly straightforward to understand, basically depending on the value of first, the value of trigger is altered.

The second example testIfElseIntegerAsPure above, highlights how the pure keyword can be used. This has a more strict use/reuse of variables and assignment. Note the use of :=? this is the assignment (but only when the 'left-hand-side' is un-set).

Range

This is a sort of if statement but with a very specific and defined use. It incorporates the EK9 concept of ranges of values. It is very useful and terse for simple situations where a developer need to check if a value is or is not within a specific range.

#!ek9
defines module introduction

  defines function

    kotlinLikeRangeCheck()
      ->
        value as Integer
        lowerBound as Integer
        upperBound as Integer

      stdout <- Stdout()

      isWithin <- value in lowerBound ... upperBound
      stdout.println(`${value} is within a range of ${lowerBound} ... ${upperBound}: ${isWithin}`)

      isNotWithin <- value not in 16-2 ... upperBound-1

      stdout.println(`${value} is NOT within a range of 16-2 ... ${upperBound - 1}: ${isNotWithin}`)

//EOF

The example above shows how checks can be made on a variable to check they are (or are not) within a specific range of values. Note that the start and end values can be calculated values and also in the form of expressions.
In someways it is similar to a ternary expression or a very simple if expression (EK9 uses switch for this type of flow control - where the conditions of more complex).

If statement with boolean logic

Simple example of using combination boolean logic as part of the if statement.

#!ek9
defines module introduction

  defines function

    currentTemperature() as pure
      -> country as String
      <- temp as Integer: Integer()

      if country == "GB"
        temp :=? 20
      else if country == "DE"
        temp :=? 41

    simpleIf()
      stdout <- Stdout()

      suitableValue <- String()
      valueToTest <- 9

      if valueToTest < 10
        stdout.println(`Value ${valueToTest} is less than 10`)

      secondValue <- 21
      specialCondition <- true

      if valueToTest < 10 and secondValue > 19 or specialCondition
        stdout.println("Test OK")

      //The same logic as above - but in a different layout.
      if valueToTest < 10 and
      secondValue > 19 or
      specialCondition
        stdout.println("Test OK")

      //Rather than use the keyword 'if' you can use 'when'
      when valueToTest < 10
        stdout.println(`Value ${valueToTest} is less than 10`)

      //As you would expect it is possible to chain if and else statements
      if valueToTest > 9
        suitableValue: "Too High"
      else if valueToTest < 9
        suitableValue: "Too Low"
      else
        suitableValue: "Just Right"

      stdout.println(suitableValue)
//EOF

This example (above) shows alternative syntax and layout and combination logic expressions.

If statement with assignment/guard and declaration

In the same sort of way the switch (see later) supports guard, assignment and declarations as part of its structure, the if statement has the same features.

#!ek9
defines module introduction

  defines function

    currentTemperature() as pure
      -> country as String
      <- temp as Integer: Integer()

      if country == "GB"
        temp :=? 20
      else if country == "DE"
        temp :=? 41

    assignmentInIf()
      stdout <- Stdout()

      //Note that this value is 'unset' in the sense it has no meaningful value
      selectedTemperature <- Integer()

      //See if we can find some hot temperature somewhere so in the US we have no idea! because value is unset
      when selectedTemperature := currentTemperature("US") with selectedTemperature > 50
        stdout.println(`Temp of ${selectedTemperature} a little warm in the US`)
      else when selectedTemperature := currentTemperature("US") with selectedTemperature < 50
        stdout.println(`Temp of " + ${selectedTemperature} not too bad in the US`)
      else when selectedTemperature := currentTemperature("GB") with selectedTemperature > 40
        stdout.println(`Temp of " + ${selectedTemperature} a little warm in the UK`)
      else when selectedTemperature := currentTemperature("DE") with selectedTemperature > 40
        stdout.println(`Temp of ${selectedTemperature} a little warm in the DE`)
      else
        stdout.println("Not sure where it might be warm")

    guardedAssignmentInIf()
      stdout <- Stdout()

      selectedTemperature <- Integer()

      //Here we use a guarded assignment checks for null and unset and only then does the conditional check
      //Also note we can still use 'if' and rather than 'with' use 'then'
      when selectedTemperature ?= currentTemperature("US") with selectedTemperature > 50
        stdout.println("Temp of " + $selectedTemperature + " a little warm in the US")
      else when selectedTemperature ?= currentTemperature("US") with selectedTemperature < 50
        stdout.println("Temp of " + $selectedTemperature + " not too bad in the US")
      else if selectedTemperature ?= currentTemperature("GB") with selectedTemperature > 40
        stdout.println("Temp of " + $selectedTemperature + " a little warm in the UK")
      else when selectedTemperature ?= currentTemperature("DE") then selectedTemperature > 40
        stdout.println("Temp of " + $selectedTemperature + " a little warm in the DE")
      else
        stdout.println("Not sure where it might be warm")

    declarationInIf()
      stdout <- Stdout()
      when selectedTemperature <- currentTemperature("US") with selectedTemperature > 50
        stdout.println("Temp of " + $selectedTemperature + " a little warm in the US")
      else when selectedTemperature ?= currentTemperature("US") with selectedTemperature < 50
        stdout.println("Temp of " + $selectedTemperature + " not too bad in the US")
      else if selectedTemperature := currentTemperature("GB") with selectedTemperature > 40
        stdout.println("Temp of " + $selectedTemperature + " a little warm in the UK")
      else when selectedTemperature: currentTemperature("DE") then selectedTemperature > 40
        stdout.println("Temp of " + $selectedTemperature + " a little warm in the DE")
      else
        stdout.println("Not sure where it might be warm")
//EOF

If statement summary

The main points in the examples of 'if statements' above are:

Note that if there are multiple if/else statements and those have declarations of variables, those variables are then available in other else/if scopes after the declaration (as part of the main if scope block). But sure you want to do this - as it may become confusing or hard to refactor later.

Being able to incorporate an assignment within the if or else if really does help to reduce the number of variables that are needed to created and assigned before the if statement block. The guarded assignment is particularly useful as the condition part of the if statement is not evaluated if the guarded assignment resulted in isSet == false.

In general, the use of the declaration/assignment or guarded assignment reads better with when rather than if, but this is developer choice. As you can see, the combination of ternary operators and the addition of assignments and guarded assignments really adds quite a lot of functionality to conditional flow control. This is augmented further in the switch statement that follows.

The guarded assignment is very useful for dealing with methods or functions that may return un-set values as they keep the additional checks much simpler.

The next section covers the switch statement/expression - which is designed to take over when there are many conditions to consider.

Switch

The switch in EK9 is similar in someways to the if/else type flow; the ordering of cases is important. It also supports multiple and varied matches and can be used to return a value like an expression. Just like the if statement it can also use alternative keywords.

When the switch is used an enumeration it works more like a traditional switch as seen in other programming languages. Indeed, it then mandates that all enumeration values are handled by a specific case (and default condition must also be provided).

Most importantly the switch statement can also be a switch expression - this means that the switch can actually return a value. Importantly there must always be a default case with the switch.

The examples below show some different uses of the switch statement/expression.

Switch on Enumerated Values

This is the most similar use of a switch statement when compared to other programming languages, but even then it has some very strict semantics.
When used with an 'enumeration', where the case statements refer directly to an enumerated value - all enumerated values must be defined in a case.
A default must also be provided for situations where the value being 'switched over' is un-set.

The switch is shown below in 'expression' form, but can also be used as a 'statement'.

#!ek9
defines module introduction
  defines type

    <?-
      This is the enumeration that is used in the example below.
      It is finite in the number of values it can hold.
    -?>
    LimitedEnum
      A,
      B,
      C,
      D

  defines function

    SimpleExhaustiveEnumerationExpressionSwitch()
      -> val as LimitedEnum

      result <- switch val
        <- rtn as String?
        case LimitedEnum.A
          rtn: "Just A"
        case LimitedEnum.B
          rtn: "Just B"
        case LimitedEnum.C
          rtn: "Just C"
        case LimitedEnum.D
          rtn: "Just D"
        default
          rtn: "Val is not set"

      assert result?
//EOF

The example above, shows the definition of a simple 'Enumerated type', then when provided with a variable 'val' which is of the 'Enumerated type' LimitedEnum; a switch expression is used to compare that value with each of the values in the 'Enumerated type'. Note the use of the returning values from the switch expression.

When used this way switch requires all enumerated values to appear in a case and a default statement. In this way the switch is exhaustive in its semantic checking. The EK9 compiler will issue errors if not all enumerated values are present or if the default statement is missing.

You might consider this too 'strict', but it has a significant advantage in highlighting common errors that present themselves when additional values are added to the 'Enumerated type'. In this situation existing code will fail to compile when an additional enumerated value is added to the 'Enumerated type'. This is a positive situation because it now forces the developer to assess the processing.

If you do not want to deal with each enumerated value explicitly, there is a simple and elegant solution to this. This is shown in the following example.

...
  defines function

    SimpleNonExhaustiveEnumerationExpressionSwitch()
      -> val as LimitedEnum

      result <- switch val
        <- rtn as String?
        case == LimitedEnum.A
          rtn: "Just A"
        default
          rtn: "Val is anything but A"

      assert result?
//EOF

By simply making one of the case statements operate as an 'expression' in this case using the == (equality) operator, the exhaustive nature of the switch statement is turned off. Now if additional enumerated values are added to the 'Enumerated type' they automatically get dealt with in the default block.

This provides a less strict switch, similar to many other programming languages.

Switch form similar to an if expression

The following example, shows how a switch expression can be employed in a similar manner to an 'if expression'.

#!ek9
defines module introduction

  defines function
    SimpleSwitchExampleWithTernaryAsAnAlternative()
      -> conditionVariable as Boolean

      //This is the reason that an ifExpression is not needed.
      resultValue <- given conditionVariable
        <- result String?
        when true
          result: "Steve"
        default
          result: "Not Steve"

      assert resultValue?

      //But above could just have been a ternary
      resultValue2 <- conditionVariable <- "Steve" else "Not Steve"
      assert resultValue2?
//EOF

The above example highlights why EK9 does not have an 'if expression', the switch is so flexible and can be used as an expression (as shown) that it provides all the functionality needed.
However, for simple expressions like this a 'ternary statement' (also shown) could have been used.

When the blocks in the 'when true' or default statements are multiline the 'ternary' would not have been usable and the switch expression with larger blocks would have been more appropriate.

Switch in statement form

The following example, shows a simple switch statement, but with expressions as part of the case statements.
The alternative syntax of given/when rather than switch/case is also shown.

This example also highlights the use of the 'pure' syntax and the necessary change in semantics to ensure variables are only assigned to when unset (this is the :=? syntax).

#!ek9
defines module introduction

  defines function

    currentTemperature() as pure
      -> country as String
      <- temp as Integer: Integer()

      if country == "GB"
        temp :=? 20
      else if country == "DE"
        temp :=? 41

    ASimpleSwitchStatement() as pure
      -> conditionVariable as Integer
      multiplier <- 5

      //This is what we will vary based on the condition variable
      resultText1 <- String()

      switch conditionVariable
        case < 12
          resultText1 :=? "Moderate"
        case > 10*multiplier
          resultText1 :=? "Very High"
        case 25, 26, 27
          resultText1 :=? "Slightly High"
        case currentTemperature("GB"), 21, 22, 23, 24
          resultText1 :=? "Perfect"
        default
          resultText1 :=? "Not Suitable"

      assert resultText1?

      resultText2 <- String()
      //The same switch could have been written using given and when
      given conditionVariable
        when < 12
          resultText2 :=? "Moderate"
        when > 10*multiplier
          resultText2 :=? "Very High"
        when 25, 26, 27
          resultText2 :=? "Slightly High"
        when currentTemperature("GB"), 21, 22, 23, 24
          resultText2 :=? "Perfect"
        default
          resultText2 :=? "Not Suitable"

      assert resultText2?

//EOF

As you can see rather than just matching absolute single values in the case, EK9 supports a wide range of matches. This makes it very important that you present the order correctly, as EK9 will only match the first in the list. It is also very important that when using functions in the match there are no side effects from those functions.

Unlike other programming languages there is no break or yield use. There is no case 'fall-through'; this catered for by allowing multiple expression conditions per case.

The switch in EK9 has been given great power and versatility - take care with it. Elect to do the simplest matching where ever possible.

Switch in expression form

This example shows a function in 'pure' form again. This time the switch has a return value and is used as an expression. The use of switch in expression form fits well with the 'pure' nature of only assigning a value to a variable once. It also shows some of the other case expression conditions.

#!ek9
defines module introduction

  defines function

    ASwitchAsExpression() as pure
      -> conditionVariable as String

      //This is more like a chained if/else with expressions in the case
      resultText <- switch conditionVariable
        <- result String?
        case 'D'
          result :=? "Inappropriate"
        case matches /[nN]ame/
          result :=? "Perfect"
        case > "Gandalf"
          result :=? "Moderate"
        case < "Charlie"
          result :=? "Very High"
        default
          result :=? "Suitable"

      assert resultText?
//EOF

In the ASwitchAsExpression example above you can see how the switch is used in an expression that both declares a new variable resultText and initialises it from the return value from the switch.

As shown above, it is possible to match String values with both lexical comparison and regular expression matches. Just to reiterate - focus on the ordering of the list in the switch statement. If you've used other languages then the switch statement tends to only match single values. EK9 introduces range matching and while this is more powerful it requires much more focus to get right.

Switch with a guard/assignment/declaration condition

The concept of a guard expression is much like a containing 'if statement'. In this example if the returning value from 'currentTemperature' was 'un-set' then the variable 'temperature' would remain 'un-set'. If that was the case then the whole switch expression would not be evaluated at all. This would mean that 'resultText' would also be 'un-set'.

The assignment and declaration form always process the switch statement, it's just the declaration is completed inside the switch scope.

#!ek9
defines module introduction

  defines function

    currentTemperature() as pure
      -> country as String
      <- temp as Integer: Integer()

      if country == "GB"
        temp :=? 20
      else if country == "DE"
        temp :=? 41

    ASwitchWithGuard()
      temperature <- Integer()
      multiplier <- 5
      resultText <- switch temperature ?= currentTemperature("GB") with temperature
        <- result String: String()
        case < 12
          result: "Moderate"
        case > 10*multiplier
          result: "Very High"
        case 25, 26, 27
          result: "Slightly High"
        case 21, 22, 23, 24
          result: "Perfect"
        default
          result: "Suitable"

      assert resultText?

    ASwitchWithAssignment()
      -> multiplier as Integer

      temperature <- Integer()
      resultText <- switch temperature := currentTemperature("GB") with temperature
        <- result String: String()
        case < 12
          result: "Moderate"
        case > 10*multiplier
          result: "Very High"
        case 25, 26, 27
          result: "Slightly High"
        case 21, 22, 23, 24
          result: "Perfect"
        default
          result: "Suitable"

      assert resultText?

    ASwitchWithDeclaration()
      -> multiplier as Integer

      resultText <- switch temperature <- currentTemperature("GB") with temperature
        <- result String: String()
        case < 12
          result: "Moderate"
        case > 10*multiplier
          result: "Very High"
        case 25, 26, 27
          result: "Slightly High"
        case 21, 22, 23, 24
          result: "Perfect"
        default
          result: "Suitable"

      assert resultText?

      //So this means the 'temperature' defined above is now out of scope and so
      //can be declared a-fresh and used here
      temperature <- "Some other value"
      assert temperature?

//EOF

One final point, the switches in EK9 must have a default and there is no fall through (and hence no break keyword like in C/C++/C#/Java). The fall through capability is delivered by allowing multiple values per case.

A lot of capability and flexibility has been added into the switch. The EK9 switch is much more like an if/else chain than a switch from C, C++ or Java.

Try Catch and Exceptions

Whilst Try, Catch and Exceptions are a form of flow control. The flow control is one of processing an error state. It is not designed and should not be used to control normal flow. In some ways Exceptions are a form of GOTO statement (as are break and return - but in a slightly more controlled manner). In general, whilst many programming languages have a widespread use of Exceptions; these tend to pervade all API's and force alterations in interfaces. EK9 prefers the use of unSet variables, Result and the careful and selective use of Exceptions. See the section on Exceptions/Error Handling for more details.

EK9 is in general much less likely to throw an Exception and is more likely to return values that are unSet or a Result type. This means that the caller must be prepared for values that have not been set. This is the general ethos in EK9. Exceptions really should be thrown in exceptional circumstances, and never as a design mechanism for flow control.

For Loops - but with extras

Loops are normally employed when need to perform the same set of operations over some set of data.
There is quite a bit of flexibility with for loops. A key point to note that with EK9, there is no break and no return statement; so you cannot exit for loops early (this is by design). See EK9 philosophy for a discussion on the reasoning for this.

For loops in EK9 are intended and designed to run from end to end; all the way through, if you are looking to stop processing some way through a loop then consider using a while loop, streaming for loops or streaming collections.

Standard For Loop

The example below shows a standard traditional loop with an integer variable. There are a couple of things to note here:

#!ek9
defines module introduction

  defines function

    ForLoopExample1
      stdout <- Stdout()

      for i in 1 ... 10
        stdout.println(`Value [${i}]`)

    ForLoopExample2
      stdout <- Stdout()

      for i in 0 ... 10 by 2
        stdout.println(`Value [${i}]`)

    ForLoopExample3
      stdout <- Stdout()

      j <- 2
      for i in 0 ... 10 by j
        stdout.println(`Value [${i}]`)

    ForLoopExample4
      stdout <- Stdout()

      j <- -2
      for i in 10 ... 0 by j
        stdout.println(`Value [${i}]`)

    ForLoopAsExpression()
      result <- for i in 1 ... 10
        <- rtn <- 0
        rtn += i
      assert result?

//EOF

Please note the spaces around '...'; these are required.

Importantly it is also possible to use a for loop in the form of an expression that returns a value. This is shown above in function 'ForLoopAsExpression'.

For Loop with other types

In the above example an Integer was used, these examples below show the for loop has much more flexibility. Indeed, it can be used with any types (even those created by EK9 developers) as long as the appropriate comparison/addition/subtraction operators are provided.

#!ek9
defines module introduction

  defines function

    FloatForLoop()
      stdout <- Stdout()

      //So you could do a calculation to get this value
      incrementer <- 6.3
      for i in 8.2 ... 30.0 by incrementer
        stdout.println(`Value [${i}]`)

      //descending
      for i in 90.0 ... 70.0 by -5.0
        stdout.println(`Value [${i}]`)

    TimeForLoop()
      stdout <- Stdout()

      //From 9-5:30 every half hour
      start <- Time().startOfDay() + PT9H
      end <- Time().endOfDay() - PT6H30M
      thirtyMinutes <- PT30M

      //Be aware that time loops around.
      for i in start ... end by thirtyMinutes
        stdout.println(`Value [${i}]`)
//EOF

Hopefully now you can see the additional power and flexibility the EK9 for loop has.

For Loop with collections

Unlike some other Object-Oriented languages; EK9 does not attach for loop syntax to Collection objects, nor does it attach stream syntax.
It approaches the traversal of objects held within Collections in two different, but complementary ways. The first of which is shown below and the second is shown in streaming collections

#!ek9
defines module introduction

  defines function

    CollectionsForLoop()
      stdout <- Stdout()

      stdout.println("Strings")
      for item in ["Alpha", "Beta", "Charlie"]
        stdout.println(item)

      stdout.println("Characters")
      //The alternative is when you already have an iterator
      moreItems <- ['A', 'B', 'C']
      for item in moreItems.iterator()
        stdout.println(item)
//EOF

The loop variable's type is inferred from the Collection or Iterator. The for loop on Collections can be used with any type that has an iterator() method that returns an Iterator or with an object with type that has two methods below (which is a bit of a 'Duck Type' approach):

The example above shows a List of String being used in a for loop and also the Iterator from a List of Characters.

For Loop assignments, guards and declarations

In the same way the 'assignments', 'guards' and 'declarations' can be used with if and switch statements/expressions, they can also be used with for loops. This is shown below.

#!ek9
defines module introduction

  defines function

    messagePrefix()
      <- rtn <- "SomePrefix-"

    forLoopWithGuard()
      stdout <- Stdout()

      prefix as String?
      for prefix ?= messagePrefix() then i in 1 ... 10
        stdout.println(`${prefix}${i}`)

    forLoopWithAssignment()
      stdout <- Stdout()

      prefix as String?
      for prefix: messagePrefix() then i in 1 ... 10
        stdout.println(`${prefix}${i}`)

    forLoopWithDeclaration()
      stdout <- Stdout()

      for prefix <- messagePrefix() then i in 1 ... 10
        stdout.println(`${prefix}${i}`)

      //Just to highlight that 'prefix' in the for loop - was scoped in the for loop only.
      prefix <- 21
      assert prefix?
//EOF

The capability to add variable initialisation as part of the for loop makes is consistent with most of the other EK9 flow control syntax.

Streaming 'For'

There are times when the values generated by a for loop are useful in a processing pipeline. The example below shows this with the addition of some filtering.

#!ek9
defines module introduction

    workHours() as pure
      -> time as Time
      <- workTime as Boolean: time < 12:00 or time > 14:00

  defines program

    TimePipeLine()
      stdout <- Stdout()

      //From 9-5:30 every half hour
      start <- Time().startOfDay() + PT9H
      end <- Time().endOfDay() - PT6H30M
      thirtyMinutes <- PT30M

      for i in start ... end by thirtyMinutes | filter by workHours > stdout
//EOF

In the example above, the for loop is used to generate a sequence of times and these are then 'piped' into a processing pipeline and processed by the function 'workHours'. The pipeline then looks at the result of the filter and only outputs the time to the next stage of processing if 'true'. Hence, 'stdout' only gets the times that are before 12:00 and after 14:00.

Hopefully from this example you can see how the omission of break and return doesn't really present a major issue and how you can un-bundle nested loops (always tricky) into processing pipelines. But it also means that the bit of logic you might have nested in the for loop has been pulled out to a function (that can be tested, re-used and altered with ease).

This different syntax and approach does take a little time to get used to. If you notice, where an if statement to determine the work hours, this has been implemented with a simple boolean expression (predicate) in the function 'workHours'. While you may think this a trivial point - after all you still had to write the same boolean expression. You also had to write the function which is much more text than 'if'. The overall approach has moved from being procedural to being more declarative.

By using head with the pipeline processing you can cut short the streaming of objects through the pipeline. As soon as head has process the number of objects you requested it will trigger the shutdown of the pipeline. This is very much like break and return. So if; for example you were looking for a particular value in a collection or a generated list, you can use a filter to select the right one; then head to stop processing once one value with that criteria comes through the pipeline. Clearly it may be the case that the value never comes through the pipeline (if the value was not contained).

One final point; the Streaming 'For' is the main reason there are no List Comprehensions in EK9. While the syntax in EK9 and the need for functions means more code is required (than List Comprehensions in Python), it makes the code more readable and fits the EK9 philosophy.

This approach also brings standardisation to functional processing flow. So unlike other languages (such as Java) where specific methods are attached to specific 'Functional types', EK9 has the functional processing syntax built in to the language itself.

Streaming Collections

As with the for loop above and the streaming for it is possible to send all the contents of collections through a processing pipeline rather than just looping over them.

#!ek9
defines module introduction

  defines program  
    collectionsPipeLine()
      stdout <- Stdout()

      items <- ["Alpha", "Beta", "Charlie"]
      cat items > stdout

      //The alternative is when you already have an iterator
      moreItems <- ['A', 'B', 'C']
      iter <- moreItems.iterator()
      cat iter > stdout

      //An example of 'teeing' content out of a processing stream
      capturedValues <- List() of String
      cat ["Alpha", "Beta", "Charlie"], ["Delta", "Echo"] | tee in capturedValues > stdout
      assert capturedValues?

//EOF

By introducing the cat command syntax, EK9 has removed the need to attach for/stream syntax to collection classes themselves. This gives much more flexibility and consistency in developed code. In effect the developer just 'streams' the contents of a collection (or some sort of source, like an iterator) into a processing pipeline.

In the example above there is no real processing in the pipeline, the contents are just written to stdout.
But part of the example above shows two 'lists of Strings' being streamed, tee off into a collection to be used later and also streamed to stdout. There is much more detail on pipeline processing in section Streams/Pipelines.

While and Do/While loops

The final two loop flow control mechanisms are traditional in approach. They both work on the value of a conditional boolean check. But they too, accommodate 'assignment', 'guard' and 'declarations'.
They too can also be used as expressions and can return values.

While loop examples

An example of a while loop, iterating over a collection of Characters, this includes a demonstration of the use of 'guard', 'assignment' and 'declaration syntax as well.

The final function 'whileLoopAsExpression' shows the while loop being used as an expression and in conjunction with a variable 'declaration'.

#!ek9
defines module introduction

  defines function

    collectionsWhileLoop()
      stdout <- Stdout()

      moreItems <- ['A', 'B', 'C']

      stdout.println("Characters while")
      itemIter <- moreItems.iterator()
      while itemIter.hasNext()
        item <- itemIter.next()
        stdout.println(item)

      //Now a while with a guard, so reuse the iter from above but reset
      while itemIter ?= moreItems.iterator() then itemIter.hasNext()
        item <- itemIter.next()
        stdout.println(item)

      //Same again but with an assignment
      while itemIter := moreItems.iterator() then itemIter.hasNext()
        item <- itemIter.next()
        stdout.println(item)

      //This time declare a new iterator, but within the while loop
      while itemIterX <- moreItems.iterator() then itemIterX.hasNext()
        item <- itemIterX.next()
        stdout.println(item)

    whileLoopAsExpression()
      result <- while complete <- false with not complete
        <- rtn <- 0
        rtn++
        complete: rtn == 10
      assert result?

//EOF

Do/While loop example

This processing is similar to the above except the conditional logic is at the end of the processing block.

#!ek9
defines module introduction

  defines function

    collectionsDoWhileLoop()
      stdout <- Stdout()

      moreItems <- ['A', 'B', 'C']
      itemIter <- moreItems.iterator()
      if itemIter.hasNext()
        do
          item <- itemIter.next()
          stdout.println(item)
        while itemIter.hasNext()

      //The above is a bit clumsy, so lets try this instead
      //This has the same effect, when the iterator is unset
      do itemIter ?= moreItems.iterator() then
        item <- itemIter.next()
        stdout.println(item)
      while itemIter.hasNext()

    doLoopAsExpression()
      result <- do complete <- false
        <- rtn <- 0
        rtn++
        complete: rtn == 10
      while not complete

      assert result?

      //Just to show that 'complete' is bound into the do/while scope
      complete <- "Almost"
      assert complete?
//EOF

As can be seen from the examples above the 'guard' expression part when used with a do/while statement/expression makes the processing block slightly simpler. The do/while expression is also a common sort of programming 'pattern' and again the 'declaration' in the do/while makes this much more self contained.

Summary

EK9 has a wide range of flow control features, but it has deliberately excluded some of the more traditional syntax (like break for example). This is by design, as it is viewed that some of these elements lead to complexity and defects (a bit like GOTO).

However, EK9 has added new syntax to traditional flow control syntax, this mainly being the introduction of:

The use of ternary operators and assignment coalescing should be adopted in place of very simple if/else statements; they are short/succinct and expressive. However, where a range of different methods or functions need evaluation the guarded assignment in conjunction with the if statement can reduce the amount of processing.

Finally if you find you need to chain many if else if statements together then the use of a switch statement might be appropriate. Beyond the switch it is sometimes best to adopt a Dictionary/Map to solve the problem.
While this may seem strange, remember that you can associate a function delegate with a key as part of the dictionary. This means it is possible to lookup a function in a dictionary and then call that function.

It is this latter concept, that if you really do have unique values to switch on, then use a Dictionary/Map - you will probably find it is faster and easier to understand. The use of switch is designed to make larger chained if/else blocks easier to understand.

Next Steps

The next section on Exceptions and try blocks show how error flow control can be implemented.