Skip to content

Statements

Statements

Statements in Morpho are the basic unit of a program: they are executed one after another as the program is run. Like other C-family languages, statements are organized into code blocks using curly brackets:

{
  var foo = "Hello World"
  print foo
}

Any variables created in a code block, referred to as local variables, cease to exist once the code block is over. Hence this example

{
  var foo
}
print foo // Throws an error

throws a SymblUndf error.

Code blocks are themselves statements and hence can be nested arbitrarily

{
  var foo = 1
  {
    var boo = 2
    print foo + boo
  }
}

Variables defined in an outer block are visible to code in an inner block, so both foo and boo are visible to the print statement, but the converse is not true.

As can be seen in the above example, it is common stylistic practice to indent statement within code blocks using tabs or spaces. In Morpho, and most other languages with the important exception of Python, indentation is purely aesthetic and done to improve readability; it has no special syntactic meaning.

Declarations

An important category of statements are declarations, which define various kinds of construct. Variable declarations were already introduced in Chapter Variables and types. Function declarations will be described in Chapter Functions and Class declarations in Chapter Classes and Objects.

Expression statements

Any expression on its own is also a valid statement. Hence, assignment, function and method calls, etc., which are are all expressions, are also statements

a=5
foo("boo")
stack.pop()

Morpho provides a simple way of producing output through the print keyword. The expression after print is output, most commonly to the Terminal if the terminal app is being used

print log(10)

Some objects are able to display themselves in a user-friendly manner (sometimes called "pretty printing"). Printing a List for example

print List(1,2,3)

displays something a List displayed in the Morpho syntax: [ 1, 2, 3 ]. Other objects don't provide this

print Object()

simply displays a placeholder <Object>.

Print statements are provided primarily for convenience and always follow the output with a newline. For more control over printing, the System class provides additional functionality as described in Chapter XXX.

Control structures

Morpho provides a typical variety of control structures, which control the order in which code is executed. Control blocks can conditionally execute code ( if ... else ), repeatedly execute code (for, while, do ... while), break out of a loop or continue to the next iteration. Morpho also provides a mechanism (try ... catch) to handle errors that are foreseen by the programmer.

If...else

An if statement evaluates the condition expression, and if it is true, executes the provided statement

if (a<0) a*=a

Note that, as discussed above in Section [sec:Logical-operators]{reference-type="ref" reference="sec:Logical-operators"}, wherever a condition test is performed in Morpho, all values (including the are considered to be equivalent to true other than false or nil.

The statement to be executed is often a code block

if (a.norm() < epsilon) {
  print "Converged"
}

You can provide a second statement using the else keyword that is executed is the condition test was false

if (q>0) {
  print "Positive definite"
} else {
  print "Not positive"
}

It's possible to chain if and else together to perform multiple tests, one after the other as in this fragment of a calculator

if (op=="+") {
  r = a + b
} else if (op=="-") {
  r = a - b
} else if (op=="*") {
  r = a * b
} else if (op=="/") {
  r = a / b
} else {
  print "Unknown operation"
}

Note that only one code block will be executed in such an if ... else tree.

For loops

A for loop is used to iterate over elements of a collection. You specify an iteration variable and the collection to iterate over enclosed in parentheses and using the in keyword; this is then followed by a statement to be repeatedly executed, the loop body. At each iteration, the iteration variable takes on successive values from the collection. This example prints the numbers 1 to 10 using a Range

for (i in 1..10) print i

where i is the iteration variable and here the loop body is a single print statement. Any collection can be used, for example this List of functions

for (f in [sin, cos, tan]) print f(Pi/2)

Loops may also use a code block for the body

for (r in collection) {
  // Do some processing
}

It's occasionally useful to access an integer index used to iterate over the collection, for example when working with two parallel collections.

for (q, k in lst) {
    p[k] = q
}

While loops

A while loop tests whether a condition test is true; if it is it then executes the loop body and this process is repeated until the condition test fails. They're particularly useful where the loop is modifying something as it iterates. For example, this loop prints the contents of a list in reverse order, popping them off one by one

var a = List(1..10)
while (a.count()>0) print a.pop()

This example reads the contents of a text file and prints it to the screen

var f = File("file.txt", "r") // Open file to read
while (!f.eof())  {
  print f.readline()
}
f.close()

Very occasionally, it's useful to make an infinite loop and terminate it based on a condition test somewhere in the middle. To do so, use the break keyword as will be discussed later in the chapter

while(true) {
    // ..
    if(somethingHappened()) break
    // ..
}

Do...while loops

A do...while loop is similar to a while loop, but the condition test is performed after the loop body has executed. Hence the loop body is always executed at least once. This is a skeleton Read-Evaluate-Print loop (REPL) loop that gets input from the user, processes it and displays the result, repeating the process until the user types "quit":

do {
  var in = System.readline()
  // process input
} while(in!="quit")

C-style for loops

Morpho also provides a traditional C-style for loop. These are far less commonly used relative to the more modern for ... in syntax, but are occasionally useful. They have the following structure

for (initializer; test; increment) body

incorporating four elements:

  • an initializer creates iteration variables and sets their initial variables.

  • the test condition is evaluated, and the loop terminates unless the condition is true or equivalent to true.

  • the increment is evaluated after each iteration, and is typically used to increment iteration variables.

  • the body is evaluated each iteration as for other loops.

Hence the C-style loop

for (var i=0; i<5; i+=1) print i

is equivalent to the for ... in loop

for (i in 0...5) print i

Return, break, continue

There are three keywords that transfer control to a different point in the program. The most commonly used is return, which ends execution of the current function, and returns to the calling code. You may optionally provide an expression after return, which is the result of the function as returned to the caller. Because return is best understood in the context of functions, we defer further discussion of return to the next chapter.

The break statement exits the control structure and transfers execution to the code immediately after the structure. It's usually used to terminate a loop early. In this skeleton example, the programmer wants to perform up to a specified maximum number of iterations for an algorithm, but to finish once the algorithm has converged on the result

for (iter in 1...Niter) {
  if (hasConverged()) break
  // Do some work
}
// Execution continues here

On the other hand, continue is used, exclusively in loops, to skip immediately to the next iteration. It's often useful when processing a collection of data that includes elements that should be ignored. In this example, the condition checks whether an element in the given collection is callable, and if it isn't the continue statement causes the loop to go to the next element in the collection.

for (f in collection) {
  if (!iscallable()) continue
  var a = f()
  // process the result
}

Both break and continue should be used judiciously because they transfer control non-locally to another point in the program and hence introduce the possibility of confusion. It's always possible to replace them using if, but this too can lead to tangled code. The previous code could be written as

for (f in collection) {
  if (iscallable()) {
    var a = f()
    // process the result
  }
}

but there's a tradeoffthe extra level of indentation could make the code depending on the complexity of the processing code. The programmer should always keep in mind the clarity of the code written, and use one construct or another depending on which is clearer.

Try...catch

Morpho provides a type of statement, denoting using the keywords try and catch, that enables programs to handle error conditions that may be generated at runtime. This mechanism could be used, for example, by a program to recover if a file isn't found, or a resource is unavailable. The construct will be discussed more fully in Chapter Errors.