Control Statements?

Crucial to imperative programming is the concept of control flow within a function or method. To this end a number of branching statements are provided within Concurnas. These are: if elif else, the if else expression, while, loop, for, parfor, match and with. First, lets examine the blocks which these control statements are composed of.

Blocks?

Blocks form the foundation of control flow statements. They serve two purposes:

  1. Defining scope - Variables and functions declared within a block cannot be used outside of that block. But code within a scope can use the functions and variables defined in parent nestor scopes.

  2. Returning of values.

Simply put a block is a pair of curly braces:

{
  //more code in here	
}

Concurnas also offers a compact, single line block format => which can be used as follows:

def myMath(a int, b int, c int) => a*b+c

def mypy(a int, b int) => res = a**2 + b**2; return res**.5

Blocks are able (but not obliged) to return values if their final logical line of code returns something:

result = {
  a = 2+3; 
  a**2
}//result is assigned value 25

Blocks which are used as part of control statements (soon to be elaborated) may return values as well, this becomes a very useful programming construct for creating concise units of code. For example:

result = if(cond1){
  "condition 1 met"
}elif(cond2){
  "condition 2 met"
}else{
  "no conditions met"
}

In the case where there are two or more different types returned by the individual blocks associated with a control statement, the most specific type which can satisfy all the available returned types is chosen as the overall return type of the statement:

something Number = if(xyz()){
  82//this is boxed to an Integer, which is a subtype of the more general type Number
}else{
  new Float(0.2)//Float is a subtype of Number
}

All branches must return something for the above sort of code to be valid, if at least one branch does not return something, then this approach cannot be used (unless another flow of control statement causing breakout is encountered such as return, throw etc as the final line of the offending branch).

Since blocks return values, and lambdas/functions/methods are composed of a name (except for lambdas), signature and block, then we can skip having to use the return statement in our function/method definitions.

So instead of:

def plusTwo(an int){
  return an + 2
}

We can write:

def plusTwo(an int){
  an + 2
}

In fact, with Concurnas, we can take this a step further by using the compact block form of =>:

def plusTwo(an int) => an + 2

Note that the return type in the plusTwo function definitions is inferred as int. Sometimes however this auto-return behaviour is undesirable. Say we are trying to implement a function returning void and our last expression can return something, here we can use nop ;; to suppress this being returned:

count = 0
def incrementor() => count++;;

Alternatively, we can specify the type for our function:

count = 0
def incrementor() void => count++

Anonymous Blocks?

It's often useful to specify a block on its own, without an attached control statement or association to a lambda/function/method etc. This provides one a bounded scope which is handy if one is working on a large/complex section of code and wishes to make clear a certain part of code is 'separate' functionality wise from the rest, and enable variable names to be potentially reused. It also allows one to return a value. For example:

//complex code here

oddcalc = {
  a = 23
  a*2 + a
}

anotherone = {
  a = 57
  b = 99
  a*b-1
}

In fact, this turns out to be especially useful functionality in so far as defining isolates is concerned. For it enables us to write this kind of code:

res = {
  //complex code here
  a= 99
  a + 1
}!//Use the ! operator to create an isolate

If elif else?

If, else, if else is a branching control statement. It is composed of a if test and block, optionally any number of elif (or else if) test and blocks and may optionally include an else block for when all the if test and any defined elif tests resolve to false. For example:

def fullif(an int) String {
  if(an == 1){
    "one"
  } elif (an==2){
    "two"
  } else if(an ==3){//'elif' and 'else if' are considered to be syntactically identical
    "three"
  } else{
    "other"
  }
}

def ifelse(an int) String {
  if(an == 1){
    "one"
  } else{
    "other"
  }
}

def ifelif(an int) String {
  res = "unknown"
  if(an == 1){
    res = "one"
  } elif (an==2){
    res = "two"
  }
}

def justif(an int) String {
  res = "unknown"
  if(an == 1){
    res = "one"
  }
}

In the final two examples above ifelif and justif, since no else block is provided, there is no certainty that all paths will resolve to return a value (it's possible that all the if and elif tests will fail) - so we cannot return a value from the if statements.

If else expression?

The if else expression, whilst not a statement, is translated into a if elif else statement (without any elif units) and behaves otherwise identically to an if-elif-else statement. An if test and else case must be included. For example:

avar = 12 if xyz() else 13

Functionally, this is identical to writing:

avar = if(xyz()){ 12 } else{ 13 }

Single expression test?

In Concurnas, a expression may be referenced in isolation within a test requiring a boolean value, e.g. an if, elif or while test expression:

def gcd(x int, y int){
  while(y){//translated to y <> 0
    x, y = y, x mod y
  }
  x
}

In cases such as these where the value resulting from evaluating the test expression is non boolean and in fact integral in nature, this is compared against the value 0, if the result is non zero the expression resolves to true.

toBoolean?

As a corollary and in aid of our Single expression test above, all objects in Concurnas have a toBoolean method automatically defined. This returns a single boolean value resolving to true. This value method may be overridden, for instance if one were defining a data structure, one may wish to define the toBoolean as returning false if the structure were empty.

The toBoolean method is called when a single object is referenced where a boolean value is expected. Additionally, if the object is nullable, then it is checked for nullability, only if these two conditions are met is a value of true returned. For example:

class MyCounter(cnt = 0){
  def inc(){
    cnt++
  }
  override toBoolean(){
    cnt > 0
  }
}

counter = MyCounter()

result = if(counter){//equivilent to: counter <> null and counter.toBoolean()
  'counter is greater than one' 
}else{
  'counter is zero'
}

The behaviour for Strings, arrays, lists, sets and maps in Concurnas is slightly different from the above. In order to return true for:

  • Strings. The value must be non-null and non zero (i.e. not an empty String)

  • Arrays. The value must be non-null and the length field must be greater than 0.

  • Lists, sets and maps. For java.lang.List, java.lang.Set, java.lang.Map's the value must be non-null and a call to the isEmpty method must return false.

Loop?

The loop statement is our first repeating control statement. It will execute the block of code attached to it repeatedly until either the code breaks out from the loop (by using the return, break or by throwing an exception) or the program is terminated - which is the less common use case. For example:

count=0
loop{
  System out println ++count
  if(count == 100){
    break
  }
}

While?

The while statement is our second repeating control statement. It behaves in a similar way to loop but has a test at the start of the loop which if passed results in the attached block being called, at the end of the block the test is attempted again and if it passes then the block is called again, etc. If the test ever fails then repetition ceases. For example:

count = 0
while(++count <= 100){//test
  System out println count
}//block to execute

For?

The for loop statement is our third and final repeating control statement. There are two variants of for loop: c style for and iterator style for.

C style for?

C style for is the syntactically older of the two variants of for loop. In addition to its main block to repeat, it is composed of three key components:

  1. An initialization expression, this is executed once at the start of the for loop. If a variable is created here it is bounded to the scope of the main block.

  2. A termination expression. For as long as this resolves to true the loop is repeated, it is executed at the start of each potential repetition of the loop.

  3. A post expression. This is executed at the end of the loop.

Here is a typical instance of a c style for loop:

for(an = 0; an < 10; an++){
  System out println an	
}

Note that it's perfectly acceptable to omit any or all of the key components above, the following is equivalent to our loop example explored previously:

count=0
for(;;){
  System out println ++count
  if(count == 100){
    break
  }
}

Iterator style for?

The iterator style for is great for performing an operation per instance of an item in a list, array or otherwise iterable object - which is often the case about 80% of the time in modern programming with for loops. Instead of the three separate key components as par the c style for loop above, we just have one, an iterator expression using the in keyword, which creates a new variable with scope bound to the main block of the for loop:

for(x in [1 2 3 4 5 6 7 8 9 10]){
  System out println x
}

for(x int in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]){//here we choose to specify the type of our new variable x
  System out println x
}

In the above instances we are choosing to iterate over an n dimensional array and a list respectfully. We can also iterate over any object that implements the java.util.Iterator interface, for example a range object:

for(x in 1 to 10){
  System out println x
}

Parallel for?

parfor looks a lot like a repeating control statement and indeed it does largely behave like a for loop in so far as iteration is concerned - indeed, both styles of for iteration are permitted, c style and iterator style. But execution of the main block is quite different. parfor will spawn the maximum number of isolates it sensibly can (i.e. no more than one per processor core), and will assign each of the iterations of the for loop to these isolates in a round robin fashion, equally distributing the workload. Let's look at an example:

result1 = parfor(x=1; x <= 100; x++){
  1 + x*10
}

result2 = parfor(x in 1 to 100){
  1 + x*10
}

Bear in mind that code expressed in the main block that attempts to interact with the iteration operation (i.e. the value of x in the first parfor statement above) will not behave in the same way as an ordinary for loop. Likewise, break and continue are not permitted within a parfor loop because the execution of the main block across a number of differing isolates occurs on a concurrent and non deterministic basis.

Instead of returning a java.util.List<X> object, as par a regular for loop, an array of ref's corresponding to each 'iteration' of the main block is returned of type: X:[] - where X is the type returned from the main block.

Parforsync?

parforsync behaves in the same way as parfor, but all the ref's returned from the 'iterations' of the main block must have a value set on them before execution may continue past the statement - i.e. all iterations must have completed. Example:

result1 = parforsync(x=1; x <= 100; x++){
  1 + x*10
}

result2 = parforsync(x in 1 to 100){
  1 + x*10
}

Repeating Control Statements Extra Features?

The Repeating control statements; loop, while, for, parfor and parforsync share some common extra features which make them especially useful.

else block?

An else block can be attached to all the repeating control statements, except loop, parfor and parforsync, and allows one to cater for cases where the attached main block is never entered into (i.e. the relevant test resolves to false on the first attempt):

res = ""
while(xyz()){
  res += "x"	
}else{
  res = "fail"
}
res = ""
for(x in xyz()){
  res += x	
}else{
  res = "fail"
}

Returns from repeating control statements?

The repeating control statements may return a list of type java.util.List<X> where X is the type returned by the attached loop block - int in the below example:

count=0
series = loop{
  if(count == 100){
    break
  }
  ++count//returns a value of type int
}

For series above the type is java.util.List<int>

when used in a for loop:

series = for(a in 1 to 100){ a }

Note that parfor and parforsync have their own, different, return logic covered previously.

The index variable?

All the repeating control statements (except the c style for loop) may have an attached index variable. This is handy in cases where one say wishes to use the Iterator style for loop, but also have a counter for the number of iterations so far. The index may have any name and by default the type of the index is int. long is a popular choice if you need to explicitly define the type of the index where working with very large iterations. An initial value assignment expression may be defined, if one is not then the value of the index is set to 0. For example, with a for loop:

items = [2 3 4 5 2 1 3 4 2 2 1]

res1 = for(x in items; idx) { "{x, idx}" }//idx implicitly set to 0
res2 = for(x in items; idx long) { "{x, idx}" }//idx implicitly set to 0L
res3 = for(x in items; idx = 100) { "{x, idx}" }
res4 = for(x in items; idx long = 100) { "{x, idx}" }

alreadyIdx = 10//index tobe used defined outside of loop
res5 = for(x in items; alreadyIdx) { "{x, alreadyIdx}" }//alreadyIdx already defined

Index variables may be applied to loops and while loops in a similar fashion to iterator style for loops:

items = [2 3 4 5 2 1 3 4 2 2 1]
n=0; res1 = while(n++ < 10; idx) { "{n, idx}" }//idx implicitly set to 0
n=0; res2 = loop(idx) {if(n++ > 10){break} "{n, idx}" }//idx implicitly set to 0

With statement?

The with statement which, though not being an actual control statement, does look a lot like one, and for that reason is included here. This is particularly useful in cases where one must call many methods, or interact with many fields of an object in a block of code. Using the with statement allows us to skip having to explicitly reference the object variable of interest:

class Details(~age int, ~name String){
  def getSummary() => "{name}: {age}"
  def rollage() => age++;;
}

det = new Details(23, "dave")

with(det){
  name = "david"
  rollage()
}

det.getSummary()
/
=> "david: 24"

With statements, as with all block based code in Concurnas can return values:

class Details(~age int, ~name String){
  def getSummary() => "{name}: {age}"
  def rollage() => age++;;
}

det = new Details(23, "dave")

summary = with(det){
  name = "david"
  rollage()
  getSummary()
}
//summary == "david: 24"

With statement may be nested. In instances where there is an identically callable method in the nesting layers, then the innermost layer is prioritized.

Break and Continue?

The break and continue keywords may be used within all the repeating control statements: loop, while, for (but not parfor or parforsync) in order to affect the flow of control within the main block of the statements.

The break keyword allows us to break out (escape from) from the inner most repeating control statement main block, and continue on with execution of the code post the control statement.

The continue keyword allows us to terminate the current repetition of the inner most repeating statement main block and carry on execution as if we had reached the end of the main block attached to the statement normally - so for a loop this would be the start of the main block, for a while it would be the beginning of the test etc.

Let's look at an example of continue and break:

items = x for x in 1 to 50

for(x in items){
  if(x <== 5){
    continue //go back to the start of the loop, i.e. skip the code which outputs to console
  }elif(x == 9){
    break//go to the code after the for loop
  }
  System out println x
}

The continue and break keywords may be used when the repeating control structures which return a value for each iteration in the following manner:

series = for(x in items){
  if(x <== 5){
    continue x//continue but add a value to the result list
  }elif(x == 6){
    continue//continue and don't add a value to the result list
  }elif(x == 9){
    break x//break but add a final value to the result list
  }elif(x == 10){
    break//break and don't bother to return a value to the result list
  }
  x//if we've made it throught the above, then this value is added to the result list
}