[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

4. The Algae Language

In Algae, variables and operators combine to form expressions and statements. The rules for this are mostly conventional; for example, the statement a=b+c means that the sum of b and c is assigned to the variable a. In Algae, however, the concept of addition (as well as many other operations) is expanded to include operations between a variety of data classes.

4.1 Entities  
4.2 Variables  
4.3 Operators  
4.4 Expressions  
4.5 Statements  
4.6 Functions  


4.1 Entities

The basic unit of data in Algae is the entity. Every entity has an attribute called its class which describes it as a "scalar", "vector", "matrix", etc. The built-in function class returns the class of its argument as a character string. Thus

 
x = 1.2E3; class(x)?

prints "scalar" and

 
class (cos)?

prints "function". Naturally, the statement

 
class (class (log))?

prints "scalar", since class returns a scalar character string.

Entities also contain members, in which various additional attributes of the entity are stored. For example, matrix entities have members such as type, density, and symmetry. These members are themselves entities, so it is not unusual to have several levels of membership (a member of a member of a member). See section 5. Data, for a description of the various classes.

The "dot" operator is used to refer to members. The statements

 
x = 1; x.type?

print "integer", the value of the type member in x. The line

 
foo = 1492; foo.bar?

prints "NULL", since scalars don't contain a bar member when they're created. You could give it one, of course:

 
foo.bar = "Columbus"


4.2 Variables

A variable is a symbol that refers to a named entity; the entity is known as the symbol's value. A variable comes into being when a value is assigned to it; there are no explicit declarations.

4.2.1 Variable Names  
4.2.2 Evaluation of Variables  
4.2.3 Scope of Variables  
4.2.4 Predefined Variables  


4.2.1 Variable Names

Variables have names consisting of letters, digits, dollar signs ($) and underscores (_). A variable name cannot begin with a digit. Variable names are case sensitive, so the variables foo and Foo are distinct.

We generally use names that begin with $ to refer to global variables that are set at startup or that have some kind of side effect. For example, the variable $beep acts like any other variable, but also control's Algae's sound effects. The who function does not report variables that begin with $ unless you give it the argument "$".

One variable name is special--the variable $$ refers to the global symbol table. For example,

 
a = "x";
$$.(a)

prints the value of the global variable x. You can use $$ like any other variable, except that you cannot reassign its value.

The only limitation on the length of a variable name is that it must have at least one character. Short names are convenient for interactive work, but longer, descriptive names have advantages in functions and scripts.


4.2.2 Evaluation of Variables

When a variable name is encountered in an expression, it is evaluated by replacing the variable name with the corresponding named entity. If it is an argument to a function, then a copy is made (conceptually, if not in fact) and passed to the function. Thus, in a function call like func(a) (see section 4.6 Functions), the function func only gets a copy of a and can't modify the a belonging to its caller.

There is one exceptional case, involving the "dot" operator, in which an identifier is not evaluated as a variable name. An identifier on the right side of a member expression is taken literally as the name of the member. For example, the statement

 
v.type

prints the value of v's member called type. You could well have a variable named type, but it would not affect the member expression above.

Alternatively, the statement

 
v.(type)

has an expression to the right of the "dot" operator, not a simple identifier, and so prints the value of the member of v named by the variable type.


4.2.3 Scope of Variables

Variables referenced from within functions are global in scope unless they are arguments to the function or are declared local with the local statement. For example, the function

 
init = function ()
{
  i = sqrt (-1);
  e = exp (1);
  pi = acos (-1);
};

could be used to initialize some commonly used variables. Once you executed init(), the variables i, e, and pi would be globally defined.

At the same time, you could have another function

 
egg_hunt = function (v)
{
  local (i);
  for (i in 1:v.ne)
  {
    if (v[i] == 0) { return i; }
  }
}

in which i has local scope. Here, you can't modify or even get the value of the global variable i.

The form of the local statement is similar to a function call, and multiple variables may be specified by separating them by semicolons.

The local statement is unusual, in that it is a directive to the parser rather than something that is coded. This means that it takes effect when the parser sees it, not when it's executed. In the code

 
f = function ()
{
  a = 1;
  if (0) { local (a); }
  a = 2;
}

the global variable a gets set to 1. Even though the code in the if block is not executed, after seeing it the parser considers a to be a local variable. Normally, one simply puts local statements at the beginning of a function.

The veil statement allows you to make a temporary change to a global variable which will remain active from that point until the end of the current dynamic scope, typically until the function ends. For example, the function

 
prt = function (x)
{
  veil (pi);
  pi = "apple";
  bake ();
};

changes the value of the global variable pi while it's executing, but pi reverts to its previous value when prt finishes. If the function bake referred to pi, it would use or modify the temporary value.

By dynamic scope, we refer to the current execution unit. Within a function, that lasts as long as the function is executing. Otherwise, the dynamic scope is the current file, except that for the eval and exec functions it is the argument string itself. Also, if executing in interactive mode and not in a function, then the dynamic scope extends only to the end of the last statement on a line.

Unlike the local statement, the effect of the veil statement takes place at the time it is executed. Once a variable is veiled (and before you set it to something else), it has the same value as the associated global variable.


4.2.4 Predefined Variables

Several variables are already defined for you when Algae starts up. Some of these are used to control Algae, and some provide you information. The predefined variables are:

$beep
If this variable is "true" (that is, test($beep) returns 1), then error messages include a BEL character. This causes most terminals to beep. On startup, $beep is 0.

$digits
This variable specifies the number of significant digits that Algae prints for real and complex numbers. On startup, $digits is 4.

$pid
On startup, $pid contains the "process id" of the Algae process.

$program
On startup, $program gives the name of the current Algae program. This will normally be "algae". It would be different if, for example, you had changed the name of the Algae executable.

$prompt
This variable controls Algae's interactive prompt. It is expected to be a character vector with two elements--on startup it is

 
(  "> ", "  " )

The first element is Algae's first-level prompt. When Algae is waiting for another line of input before it executes what it has already seen, it presents its second-level prompt. The second element of $prompt defines this second-level prompt. If Algae can't make sense of $prompt's value, it does without a prompt.

$read
The $read variable is set as a side-effect of the readnum function. It reports the number of values read in the last call to readnum.

$term_width
This variable tells Algae the width of your terminal in characters. Algae will attempt to adjust its display to fit within this width. On startup, Algae will attempt to sense this on its own. If term_width is set to 0, Algae will not wrap lines.

help
This is simply a character scalar that provides a few basic instructions for using Algae.

You may wish to set some of these variables (like $beep or $prompt) every time you use Algae. This is conveniently done by putting the assignments in the `.algae' file in your home directory.


4.3 Operators

Algae has unary, binary, and ternary operators to perform a variety of operations. The standard arithmetic operators (`+', `-', `*', `/', etc.) are available and have the usual precedence. The caret `^' is for exponentiation: 8^2 equals 64. The single quote character `'' denotes the conjugate transpose.

With a few exceptions, all operators work in the "element by element" sense. For example, if A and B are matrices with the same size, then A/B returns a matrix, each element of which is the quotient of the corresponding elements of A and B. The `*' operator works in an "inner product" sense, so A*B is a matrix multiplication. The `@' operator is Algae's "element by element" multiplication operator.

When an operator has array operands of different types, one will be automatically converted so that they have a common type, if possible. An integer may be converted to real or complex, and a real may be converted to complex. No conversion to or from character type is possible.

Unlike some other languages, the result of an operation in Algae may have a type that is different than its operands. For example, the result of 1/2 has real type, even though its operands have type integer. Another example is (-1)^0.5, which returns a complex number.

With the exception of *, if a binary operator has both a vector and a matrix as its operands the vector is converted to a matrix before the operation is performed. Upon conversion, a vector becomes a matrix with one row. For example, in the statement

 
( 1, 2 ) + [ 3, 4 ]

the left-hand operand of + is the vector (1,2). This is converted to a matrix before being added to [3,4].

The following table summarizes the precedence and associativity of Algae's operators. Those shown on the same line have equal precedence, and the rows are in order of decreasing precedence.

operators
associativity

() [] .
left to right

'
right to left

^
right to left

! + -
(unary) left to right

* / % @
left to right

+ -
(binary) left to right

< > <= >= == !=
left to right

&
left to right

|
left to right

&&
left to right

||
left to right

:
left to right

,
left to right

= += -= *= /= @= %=
right to left

4.3.1 Function Calls  `()'
4.3.2 Element References  `[]'
4.3.3 Member References  `.'
4.3.4 Transpose  `''
4.3.5 Power  `^'
4.3.6 Not  `!'
4.3.7 Negation  `+' and `-' (unary)
4.3.8 Multiplication  `*', `/', `%', and `@'
4.3.9 Addition  `+' and `-' (binary)
4.3.10 Relation  `<', `>', `<=', `>=', `==', and `!='
4.3.11 And  `&'
4.3.12 Or  `|'
4.3.13 Short And  `&&'
4.3.14 Short Or  `||'
4.3.15 Generate  `:'
4.3.16 Append  `,'
4.3.17 Assign  `=', `+=', `-=', `*=', `/=', `@=', and `%='


4.3.1 Function Calls

Functions are called in the usual way: the function reference is followed by a list of arguments. For example,

 
w = union (u; v);

calls the function union, passing it the values of u and v for arguments. The value returned by the function's return statement (or NULL if it doesn't have one) is the value of the function call.

The arguments may be any expressions, and are separated by semicolons. The called function gets only the values of the expressions you give it as arguments, so it can't modify the variables that you give it.

From its definition, the function knows how many arguments to expect. Passing too many arguments is an error. If you pass fewer arguments than the function definition calls for, NULL's are passed in place of the missing ones.

Besides their use in function calls, parentheses are used for several other purposes, including grouping expressions, if, for, and while conditions, function definitions, etc.


4.3.2 Element References

The element reference operation returns specified vector elements or matrix rows and columns. Applied to a table, the operation is performed on each of the table's members.

Elements are referenced by following a vector or matrix expression with a bracket-enclosed list of the desired elements. For vector v, the expression v[w] gives the elements specified by w. The expression within the brackets is expected to be a scalar or a vector--if it's a matrix, it will be converted into a vector if possible. So, for example,

 
v[3:8]

prints the third through eighth elements of v. If the vector inside the brackets has a numeric type, then it is converted to integer if necessary (by taking the real part and rounding) and then used to refer to the element numbers.

The class of the element reference expression is determined by the class of the specifiers. Once any matrix specifiers are converted to vectors, the dimension of the result is equal to the sum of the dimensions of the specifiers. For example, M[1;2] is a scalar, M[[1];2] and M[1;[2]] are vectors, and M[[1];[2]] is a matrix. Notice that the class of the result does not depend on the class of the original entity.

If the vector inside the brackets has character type, then it is taken to refer to the element labels instead of the element numbers. For example, if you set x as

 
x = 1:3;
x.eid = "this", "that", "other";

then x["that"] returns 2, the value of the element of x having the label "that". If the labels do not have character type, they are temporarily converted to character type for the comparison.

Element references work the same way for matrices, except that both rows and columns are specified. For example,

 
M[ 1,3; 7:12 ]

specifies rows 1 and 3, columns 7 through 12, of the matrix M.

The specifiers need not be irredundant. For example, M[1,1,1;] returns three copies of the first row of M.

Besides their use for element references, brackets are also used to form matrices.


4.3.3 Member References

Members of an entity are referenced with the "dot" operator. For example, x.type returns the value of x's member type. Notice that, if the right-hand operand is an identifier, then it is taken literally as the name of the desired member. You might have a variable called type, but that is irrelevant when it comes to evaluating x.type.

On the other hand, if the right-hand operand is an expression, then its value (converted to a character scalar) is taken as the desired member name. Since you can change an identifier into an expression simply by enclosing it in parentheses, the expression x.(type) does use the value of the variable type as the name of the member.

Member names do not share the same limitations as variable names. In fact, any string of ASCII characters (excluding NUL) will work. This can be pretty useful. For example, you could set up a "vector" of entities (of any class) as in

 
V = {};
V.(1) = A;
V.(2) = B;
V.(3) = C;
V.(4) = D;

and then refer to the individual "elements" (that is, the members of V) by number. You could handle multiple dimensions by referring to element "3,2,4", for example.


4.3.4 Transpose

The transpose operator applies the conjugate transpose operation to a matrix. For integer, real, and character types, this means moving every element from row i and column j to row j and column i. If the matrix has complex type, the complex conjugate operation is applied as well.

If you want the transpose of a complex matrix M, and not its conjugate transpose, then use the expression conj(M').

If transpose is applied to a scalar or vector, the entity is first converted to a matrix and then transposed. For example, (1,2)' is the same as [1;2]---the vector is first converted into a matrix with one row and then transposed to form a matrix with one column.

If this operator is applied to a table, then the operation is performed on each member of that table.


4.3.5 Power

The "power" operator `^' is a binary operator that raises its left operand to the power of its right operand. Thus 2^3 gives 8. It associates right-to-left, so the expressions x^y^z and x^(y^z) are equivalent.

When vectors and matrices are involved, the "power" operator performs in an "element-by-element" sense. For example, if M is a matrix, then M^2 squares each element of M. This is definitely not the same thing as M*M!

In an expression such as 2^M, where the left operand is scalar and the right operand is a vector or matrix, the result has each element i of M replaced by 2^M[i]. For example,

 
2^(0:3)

prints the vector (1,2,4,8).

Notice that the precedence of `^' is higher than `-', so the expression -1^2 returns -1.

In mathematical usage, 0^0 is undefined--it yields an error in Algae.

If both left and right operands are arrays, then they must have matching dimensions and labels.

If this operator is applied to a table, then the operation is performed on each member of that table.


4.3.6 Not

The `!' operator is a unary, prefix operator that returns 1 if its operand is considered "false", and 0 otherwise. The "false" operands are:

If this operator is applied to a table, then the operation is performed on each member of that table.


4.3.7 Negation

The unary negation operator `-' multiplies its numeric argument by -1. The `+' operator is for user convenience--Algae ignores it in its unary context. For example, +-1 gives a negative one. On the other hand, 1+-1 and 1-+1 both return 0.

If one of these operators is applied to a table, then that operation is performed on each member of the table.


4.3.8 Multiplication

Both the `*' and the `@' operators perform multiplication. The difference is that `@' performs in an element-by-element sense, while `*' performs in an inner product sense. The operands of `@' must have matching dimensions and labels; each element of its left operand is multiplied by the corresponding element of the right operand. For example,

 
( 1, 2, 3 ) @ ( 4, 5, 6 )

returns the vector (4,10,18).

When `*' is applied to matrices, the number of columns of the left operand must equal the number of rows of the right operand, and the corresponding labels must match. The result is a matrix that has the same number of rows as the left operand and the same number of columns as the right operand.

If one of the operands of `*' is a vector and the other is a matrix, the vector is (conceptually) converted to a matrix before the multiplication is performed. If on the left, it is converted to a matrix with one row; if on the right, it is converted to a matrix with one column.

Multiplication of two vectors gives their inner product.

The `/' operator performs division. Like `@', it performs in an element-by-element sense.

If either operand of any of these operators is a scalar, then the operation is performed in an element-by-element sense. For example

 
2*(1,2,3)

gives (2,4,6)---every element of the vector is multiplied by the scalar.

The `%' operator performs the modulus operation, producing the remainder when the left operand is divided by the right operand. For example, (2,2.5,3)%2 returns the vector (0,0.5,1).

The result has the same sign as the left operand. If this operator is applied to a table, then the operation is performed on each member of the table.


4.3.9 Addition

The "addition" operators are the binary operators `+' and `-'. If their operands are numeric, then they perform the normal addition and subtraction operations.

When the `+' operator is applied to character strings, it catenates them. The `-' operator is not defined for character strings.

Between two tables, `+' combines their members in the resulting table. Conceptually, the members of the left operand are inserted in this new table first, followed by the members of the right operand. This means that if the operands have a member with the same name, the value of that member in the resulting table comes from the right operand. For example, if t is a table that contains the member "foo", then the result of

 
t + { foo = NULL }

is identical to t except that its member "foo" has the value NULL.

When the `-' operator is applied to tables, the result is a table that has all the members of the left operand except those that are also in the right operand. For example

 
{x;y;z}-{x;y}

returns a table that has the single member "z".

If just one of the operands is a table, then the operation is performed between the other operand and each member of the table.


4.3.10 Relation

The relation operators `<', `>', `<=', `>=', `==', and `!=' return "true" (1) or "false" (0) to reflect the truth of the expression. For example,

 
(1:5) < 3

returns (1,1,0,0,0).

If both operands are arrays, then their dimensions and labels must match.

Unlike most of the operators, `==' and `!=' allow NULL as an operand. In that case, the other operand is simply checked to see if it is or is not a NULL.

If one of these operators is applied to a table, then that operation is performed on each member of the table.


4.3.11 And

The `&' operator performs the logical "and" operation in an element-by-element sense. For example,

 
x = 1:5;
x > 2 & x < 4

prints (0,0,1,0,0).

If this operator is applied to a table, then the operation is performed on each member of that table.


4.3.12 Or

The `|' operator performs the logical "or" operation in an element-by-element sense. For example,

 
x = 1:5;
x < 2 | x > 4

prints (1,0,0,0,1).

If this operator is applied to a table, then the operation is performed on each member of that table.


4.3.13 Short And

The `&&' operator performs the "short-circuit" logical "and" operation. Both operands are evaluated for "truth" as if they were the test of an if statement. If the left operand is "false", then the right operand is not evaluated. If both operands are "true", the result of the operation is 1; otherwise the result is 0. Note that this operation is quite different from that of the `&' operator, which works element-by-element.

The following example code is from the solve function:

 
if (options != NULL && members (options) == "pos")
{
  A = chol (A);
else
  A = factor (A);
}

The variable options is first checked to see if it's NULL. Only if it is not NULL is the member function called. That's just what we want, since it would be an error to call member with a NULL argument.


4.3.14 Short Or

The `||' operator performs the "short-circuit" logical "or" operation. Both operands are evaluated for "truth" as if they were the test of an if statement. If the left operand is "true", then the right operand is not evaluated. If either operand is "true", the result of the operation is 1; otherwise the result is 0. Note that this operation is quite different from that of the `|' operator, which works element-by-element.

In the statement

 
if (options == NULL || options.tol == NULL) { tol = 1e-6; }

the variable options is first checked to see if it's NULL. Only if it is not NULL is the member reference options.tol evaluated.


4.3.15 Generate

Numeric vectors may be generated with the `:' operator. An expression like i:j generates a vector that starts with the value of i. If j is greater than i, each successive element is 1 greater than the previous one, with the last element less than or equal to j. If j is less than i, each successive element is 1 less than the previous one, with the last element greater than or equal to j.

The `:' operator also has a ternary form, as in i:j:k. This does the same thing as i:j except that successive elements differ by k instead of 1.

For complex operands, the operation can be described conceptually in the complex plane. A line is drawn between the points i and j. Then the resulting vector contains the points located on that line segment beginning at i, proceeding toward j, spaced a distance k (or 1, if k is not given) apart.


4.3.16 Append

The `,' operator "appends" its operands. If the operands are vectors, then the result is a new vector containing both the operands. If the operands are matrices, then the result is a new matrix containing the left operand on the left and the right operand on the right.

If this operator is applied to a table, then the operation is performed on each member of that table.


4.3.17 Assign

The assignment operators are `=', `+=', `-=', `*=', `/=', `@=', and `%='. The `=' operator returns the value of the right operand, setting the left operand to that value in the process. For example,

 
a = b = c = 1;

assigns 1 to the variables a, b, and c. The assignment operators associate right to left, so first c is given the value 1. The result of that expression, c = 1, is 1, so that value is used with b as if it read b = 1.

Notice that a test like

 
if (i = j) ...

is entirely different than

 
if (i == j) ...

In the first example, i is assigned the value of j and then that value is tested by the if statement. In the second example, the if statement tests the equality of i and j. So if i is 1 and j is 2, the first example tests true (as well as changing the value of i) and the second one tests false.

Individual elements of an array may be changed; for example

 
x[3;4:6] = 0, 0, 0;

sets to 0 the elements of x in row 3 and columns 4 through 6. The right operand is converted to the same type as the left operand, if possible, and the dimensions must agree. However, if the right operand is a scalar, then it is filled to the appropriate size. Thus, the previous example could just as well be written

 
x[3;4:6] = 0;

One more thing about array assignments: If the variable on the left is a vector, but you specify two dimensions for it, then that variable will be converted into a matrix. Likewise, if the variable on the left is a matrix, but you specify only one dimension, then it will be converted into a vector. For example, in the code

 
A = B = [ 1, 2, 3 ];
A[2] = B[;2] = 9;

A and B both begin as matrices. The second element of each array is then changed to 9. In the end, though, A is changed to a vector because only one dimension was given for it. B remains as a matrix.

The other assignment operators (`+=', `-=', etc.) are simply for convenience. The expression i+=j means the same thing as i=i+j, and i-=j means the same thing as i=i-j. If the left-hand side is an expression, you should keep in mind that it will be evaluated twice. For example,

 
x[ func() ] += 1

will actually make two calls to the function func.


4.4 Expressions

Assignments are made in the normal fashion: a=5 sets the variable `a' to contain the scalar 5. Variables are not declared prior to use, but rather take on the structure and type of whatever is assigned to them. You could, for example, enter

 
a=1;
a=[a,a];

in which case the variable `a' is first a scalar and then a matrix. A list of the previously defined variables (except functions) is given by the `who()' function, and a variable can be deleted by assigning NULL to it.

In addition to simple variables, elements and members can also be specified on the left hand side of an assignment. For example, the statement

 
A[ 1:4; 1:4 ] = 3;

assigns 3 to all of the elements of the first 4-by-4 partition of matrix `A', leaving the other elements of A unchanged. Values can be assigned to members in the same way. For example, the statements

 
A = [ 1,2,3; 4,5,6 ];
time = [ 1.1, 2.2, 3.3 ];
A.rid = [ "first"; "second" ]
A.cid = time

assign the row and column labels of `A'.


4.5 Statements

Statements are terminated by a question mark, a semicolon, or a newline. Using a question mark or newline causes the value of the statement to be printed; with a semicolon the printing is suppressed. A newline terminates a statement only if it does not occur within parentheses, brackets, or braces.

Statement termination is implied by the closing brace of an "if", "for", or "while" statement or "function" expression. Printing is enabled when the terminator is implied. For example,

 
for (i in 1:10) { i }

prints all of the integers from 1 to 10.

When real or complex numbers are printed, Algae prints 4 significant digits by default. This can be changed with the digits function, or just by setting the global variable $digits.

Comments are introduced with the `#' character. That character and any that follow it on the same line are ignored (excluding the newline).


4.6 Functions

Functions are defined by a function expression, which consists of the keyword "function", followed by a parenthesized list of arguments, followed by a set of statements enclosed by braces. For example, Algae's max function is defined by something like

 
max = function (x) { return x[imax(x)]; };

Functions are just another class of entity; the statement above defines a function and then assigns it to the variable max.

The arguments are variables that are local to the function. On entry, they take on the values of the formal arguments in the calling expression. Passing more arguments than are in the function's definition is an error. If fewer arguments are passed, then NULL's are passed in place of the missing arguments.

The function reference needn't be an identifier--a function expression works just fine. For example,

 
child = (his_functions + her_functions).reproduce();

would combine the tables his_functions and her_functions, reference the member reproduce, call it with no arguments, and then assign the result to child. Notice that, since `.' and `()' have the same precedence, their left-to-right associativity causes the member reference to occur first and then the function call. In an expression like sin(1).type, the function call is performed first.

When a function is compiled, information is included that relates its operations to the file name and line numbers of the source code. If an error occurs, this information is useful in tracking down its cause. In some cases, this information is undesirable and may be removed with the strip function. The message function is such a case.


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by K. Scott Hunziker on February, 22 2004 using texi2html