Declarations and control structures

1.Declaration and scope of variables

Constants are defined using the keyword ==:

welcome: String  == "Welcome to Mathemagix!";
ok?    : Boolean == sunny? and warm?;

Mutable variables can be defined and modified using the keyword :=, as in the following example:

i: Int := 1;
while i <= 10 step i := i + 1 do
  mmout << 10 << " * " << i << " = " << 10 * i << lf;

The skope of a variable corresponds to the innermost block delimited by { and } in which the variable is defined. For instance:

i: Int == 1;
if cond? then {
  i: Int == 2;
  foo (i, i);
}
mmout << i << lf; // i contains 1 at this point

Global variables admit the entire file as their skope. More complex scoping rules which apply in the case of multiple file projects or in presence of modules will be discussed in the chapter about programming in the large.

Regular identifiers should match the regular expression [a-zA-Z_]+[a-zA-Z0-9_$?]*. That is, the names of constants, variables, function names, macros and types should

In addition, it is customary to use lowercase identifiers for constants, variables and functions, and to capitalize the first letter of each word in identifiers for types (e.g. Integer or Sparse_Vector). Moreover, identifiers for boolean variables and predicates are usually suffixed by ?.

Besides regular identifiers, Mathemagix supports various special identifiers, such as infix + for addition. These will be discussed in more detail in the section about identifiers.

2.Declaration of simple functions

The declaration of functions will be discussed in more detail in the chapter about functions. Simple function declarations admit the following syntax:

function_name (arg_1: Type_1, …, arg_n: Type_n): Ret_Type == body;

For instance:

cube (n: Int): Int == n*n*n;

It should be noticed that this declaration is actually equivalent to the following declaration of cube as a “function constant”:

cube: Int -> Int == (n: Int) :-> (n*n*n: Int);

As in the case of ordinary variables, functions can be mutable:

foo (n: Int): Int := n*n;
foo := (n: Int) :-> (n*n*n: Int);

The return statement can be used to exit the function with a given return value. For instance:

search_index (item: Int, v: Vector Int): Int == {
  for i: Int in 0..#v do
    if v[i] = item then return i;
  return -1;
}

3.Macros

Mathemagix provides the keyword ==> for the declaration of macros. Since names of types can sometimes be rather long, macros are often used in order to abbreviate them. For instance:

POL ==> Polynomial Rational;
p: POL == polynomial (1, 2, 3);

Since such abbreviations are usually local to a file, it is customary to declare all macros at the start of the file and to use the private keyword in order to keep them private to the file. For instance:

include "numerix/rational.mmx";
include "algebrabix/polynomial.mmx";
include "algebrabix/matrix.mmx";

private {
  POL ==> Polynomial Rational;
  MAT ==> Matrix Rational;
}

// new routines on rational polynomials and matrices

It is also customary to capitalize all letters in names of macros.

Remark 1. Macros with arguments are not yet supported by the compiler, but planned.

4.Conditional statements

Conditional statements are of one of following two forms

if condition? then then_body
if condition? then then_body else else_body

The bodies of the then-part and the else-part can either be single instructions or blocks of instructions braced into {}. For instance, we may write

if done? then {
  clean_up ();
  return;
}
fib (n: Int): Int == {
  if i <= 1 then return 1;
  else return fib (n-1) + fib (n-2);
}

Notice that the if-then-else construct can also be used as an expression:

mmout << "Take a " << (if warm? then "shirt" else "jacket") << lf;

5.Simple pattern matching

It often occurs that a list of actions has to be undertaken depending on the value of some expression. This kind dispatching can be achieved using the match instruction which has the syntax

match expression with match_body

where match_body is a sequence of cases of the following form:

case case_pattern do case_body

For instance:

fib (n: Int): Int ==
  match n with {
    case 0 do return 1;
    case 1 do return 1;
    case _ do return fib (n-1) + fib (n-2);
  }

This is a simple example of the general mechanism of pattern matching, which will be discussed in more details in the chapter about abstract data types.

6.Loops

Loops are constructed as follows:

loop_modulator_1 … loop_modulator_n do loop_body

where the loop modulators are among one of the following five types:

for variable: T in values
for instruction
while condition?
until contition?
step instruction

A simple example of how to use the for-in modular is the following:

for i: Int in 1 to 10 do
  mmout << i << " * " 10 << " = " << i * 10 << lf;

The expression 1 to 10 is an example of a “generator”. For more information on such objects, we refer to the section on generators. The for-in loop is equivalent to the following one which uses the for, while and step modulators:

for i: Int := 1 while i <= 10 step i := i + 1 do
  mmout << i << " * " 10 << " = " << i * 10 << lf;

The for modulator (without in) is really syntactic sugar: the above code is essentially the same as

i: Int := 1;
while i <= 10 step i := i + 1 do
  mmout << i << " * " 10 << " = " << i * 10 << lf;

except that the scope of the variable i is bound to the body of the loop when using the for modulator. Similarly, the step modulator could have been moved to the body of the loop:

for i: Int := 1 while i <= 10 do {
  mmout << i << " * " 10 << " = " << i * 10 << lf;
  i := i + 1;
}

However, this way of writing things is slightly longer and less readable. Furthermore, this kind of rewriting becomes less straightforward in presence of continue instructions (see below).

The condition of the while modulator is tested each time before executing the body of the loop. By contrast, the condition of the until modulor is tested only at the end of the loop. For instance, in the following loop, the body is executed at least one time:

until i > 10 do {
  mmout << "i= " << i << lf;
  i := i + 1;
}

It should also be noticed that modulators of the same type can very well be used several times. For instance, the following loop will output the numbers , and :

v1: Vector Int == [1, 2, 3];
v2: Vector Int == [4, 5, 6];
for x1: Int in v1
for x2: Int in v2 do
  mmout << x1 * x2 << lf;

In this last example, even though v1 and v2 are not of type Generator Int, there exists a prefix operator @ from Vector Int to Generator Int which allow us to write for x1: Int in v1 and for x2: Int in v2.

7.Loop interruption and continuation

The execution of a loop can be interrupted using the break instruction. As soon as a break instruction is encountered, execution will resume at the end of the loop. For instance, the following loop will only output the numbers one until five:

for i: Int in 1 to 10 do {
  if i = 6 then break;
  mmout << i << lf;
}

In a similar way, the continue instruction interrupts the execution of the body of the loop, but continues with the execution of the next cycle. For instance, the following code will display all numbers from one to ten, except for the number six:

for i: Int in 1 to 10 do {
  if i = 6 then continue;
  mmout << i << lf;
}

8.Exceptions

Exceptions in Mathemagix are handled using a conventional try-catch mechanism. This mechanism resides on three keywords:

A typical example of a safe routine for printing values of a partially defined function is

print_values (f: Double -> Double): Void == {
  for x: Double in -5.0 to 5.0 do {
    try {
      y: Double == f x;
      mmout << x << "\t" << y << lf;
      catch (err: String) {
        mmout << x << "\t" << err << lf;
      }
    }
  }
}

A typical example of a corresponding partially defined function is

foo (x: Double): Double == {
  if x <= -2.0 then return x + 2.0;
  if x >=  2.0 then return x - 2.0;
  raise "out of range";
}

Applying print_values on foo yields the following output

-5      -3
-4      -2
-3      -1
-2      0
-1      out of range
0       out of range
1       out of range
2       0
3       1
4       2
5       3

Standard Mathemagix libraries usually raise errors of the type Exception instead of String. For this reason, one might wish to replace the type of err by Exception. In that case the routine foo should be replaced by

foo (x: Double): Double == {
  if x <= -2.0 then return x + 2.0;
  if x >=  2.0 then return x - 2.0;
  raise exception ("out of range", x);
}

The second argument of exception allows the user to specify a reason for the exception, such as an offending value or a line in the source code which triggered the exception.

9.Comments

Mathemagix supports two types of comments. Short comments start with // and extend to the end of the physical line:

class Complex (R: Ring) {
  re: R;  // real part of the complex number
  im: R;  // imaginary part of the complex number
  …
}

Long multi-line comments should be braced into /{ … }/ and can be nested:

y == hacked_function x; /{ FIXME:
  This is a dangerous hack /{ which occurs only on the first of april }/
  A skilled extraterrestian should be able to fix it
}/
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU General Public License. If you don't have this file, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.