Tutorial - Flow Control in Candle

Introduction

In this tutorial, we'll go through the flow control statements and expressions in Candle.

Flow control is something fundamental in any programming language. As 'good things come in pairs', Candle has two types of flow control constructs: flow control expressions and flow control statements. In this tutorial, you'll see the differences between the two and learn how to use them appropriately.

You may ask why two types of flow control constructs? Isn't that confusing or redundant? Why don't we unify everything as expressions, as in XQuery or many other functional programming languages? The reason is because Candle goes beyond functional programming, Candle also provides procedural support. And with procedural code, statement syntax is much clearer than expression syntax. Each statement means some action to be carried out, and the sequence of the statements is the sequence of execution, whereas in expression syntax, especially in functional programming, we often want to emphasize that the sequence of the operands are unimportant. In Candle, an expression is always functional. So by using two types of syntax constructs, Candle more clearly separates functional code from procedural code.

After you go through this tutorial, you'll be able to appreciate the syntax design of Candle. It is:

Variable Definition and Reference

Variable name can contain alphanumeric characters and special characters '.', '-', '_'. And variable names , like any other names, are case-sensitive.

To define and initialize a variable, you use let statement or let expression. In a Candle function, the variable value cannot be changed once it is initialized. This is a defining feature of all functional programming languages.
<?csp1.0?>
function main() {
  let time = now();
  "Now: " {time} <br/>
}
Try it yourself »
Variable definition are scoped within the statement block or the subexpression where it is defined. An inner variable definition of the same name shadows the outer definition. An element or a statement with body { ... } establish a new statement block.
<?csp1.0?>
function main() {
  let var = 123;
  "Outer var: " {var} <br/>
  <div>
    let var = 345;
    "Inner var: " {var} <br/>
  </div>
}
Try it yourself »

Flow Control Expressions

Flow control expressions are dubbed as FLWOR expressions in XQuery. The flow control expressions in Candle are similar to those in XQuery:
Expression Type Syntax Remarks
if expression if (cond-expr) then expr
else expr
The else branch is always required, even if it is an empty value;
for expression for (var) as type in expr
where expr order by expr
ascending|descending
return expr
In this beta release, you can declare only one variable in a for expression.
foreach expression foreach (expr) order by expr ascending|descending
return expr
This is a simplified version of for statement. You can use function current() to access the current item that is being processed within the expression body.
let expression let var as type = expr
return expr
Note that the value initialization operator is '=', not ':=' as in XQuery. In this beta release, you can only declare one variable in a let expression.
with expression with (cond-expr)
return expr
with expression checks if cond-expr is singular. If so, it is established as the current item, and the expression body is evaluated; otherwise, it returns empty.
switch expression switch (expr)
case expr return expr
...
default return expr
The switch expression is more like the switch statement in C/C++ and Java, but it never falls through in the case clause. And the default clause is always required, even if it is an empty value.

Here are some examples of flow control expressions:
<?csp1.0?>
function main() {
  <style>
  "span { background-color:yellow; margin:5px; }"
  </style>
  "if example: the date of today is an " {
    if (now()?day mod 2 == 0) then "even" else "odd"
  } " number" <br/>
  "let example: " copy of (let s = (1, 2, 3) return <span>{s}</span>); <br/>
  "for example: "
    copy of (for $var in (1, 2, 3) return <span>{$var}</span>);
  <br/>
  "another for example: " { for i in (1, 2) return for j in (3, 4) return (i, j) } <br/>
  "with example: " { with (1 to 10) return "you will not see me" } <br/>
  "switch example: today is " {
    switch (now()?week-day)
   
case 0 return "Sun"
    case 1 return "Mon"
    case 2 return "Tue"
    case 3 return "Wed"
    case 4 return "Thu"
    case 5 return "Fri"
    case 6 return "Sat"
    default return "error"
  } <br/>
}
Try it yourself »

Functional Flow Control Statements

Functional flow control statements in Candle are primarily used to construct markup output, whereas flow control expression are primarily used to compute values.

The flow control statements that can be used in a function are:
Statement Type Syntax Remarks
if statement if (cond-expr) { statements; }
else { statements; }
The else branch is optional.
for statement for (var) as type in expr where expr
order by expr 
ascending|descending
{ statements; }
In this beta release, you can declare only one variable in a for statement.
foreach statement foreach (expr)
order by expr 
ascending|descending
{ statements; }
This is a simplified version of for statement. You can use function current() to access the current item that is being processed within the statement body.
let statement let var as type = expr;
The scope of the variable is from after this let statement to the end of the block surrounding this statement. In this beta release, you can only declare one variable in a let expression.
with statement with (cond-expr) { statements; }
with statement checks if cond-expr is singular. If so, it is established as the current item, and the statement body is evaluated; otherwise, the statement body is skipped.
switch statement switch (expr) {
case expr { statements; }
...
default { statements; }
}
In the switch statement, it never falls through in the case clause. And the default clause is optional.
put statement put (qname = value) { statements; } This is a special statement in Candle that works like the tunneled parameter in XSLT.
to get the value pushed by the put statement context::qname A special prefix context:: is put in front of the qname of pushed value.

While all the other statements probably do no need much explanation, put statement does need a bit of attention. This is a special statement in Candle that works like the tunneled parameter in XSLT. The statement causes the value to be pushed onto a special stack with the static qname as the key. Functions inside the statement body can then get the value from this special stack using the same qname as the key. The value is popped out from the stack at the end of the statement execution. Put statements can be nested, and items on the stack can have same qname as the key, with the inner most one occludes the outer ones. Put statement saves you the trouble of passing some parameters along, when you have many deeply nested functions. Here's an example:
<?csp1.0?>
function deep-inner-func() {
  "Get value from deep inner function: " {context::my-key} <br/>
}
function inner-func() {
  "Get value from inner function: " {context::my-key} <br/>
  deep-inner-func();
}
function main() {
  put my-key = now() {
    "Putting a value on the stack in the outer function as: " {context::my-key} <br/>
    inner-func();
  }
}
Try it yourself »

Procedural Flow Control Statements

Flow control statements that are allowed functions are also allowed in methods. But there are a few procedural statements that are not allowed only in methods. They are:
Statement Type Syntax Remarks
set statement set varvalue; In functions, you cannot change the value of a variable once it is initialized. In procedural routines, you can do so. But you should follow the good practice of functional programming, and only use set statement in side while loop to change the value of the looping variable.
while statement while (expr) { statements; }
The good-old while statement in every procedural languages.
continue statement continue; This continue statement can only be used in the while statement.
break statement break;
This break statement can only be used in the while statement.
return statement return;
return expr;
If a method does not have return value, then return; should be used; otherwise, return expr; should be used.

These statements should be familiar to most programmers. And their usage shall be illustrated in the tutorial on Action Statements and Methods in Candle.