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()
Print statements
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.