Chapter 5: Expressions
5.1 Names
5.2 Literals
5.3 Definitions
5.4 Assignments
5.5 Functions
5.6 Function Calls
5.7 Imperatives
5.8 Multiple Values
5.9 Sequences
5.10 Exits
5.11 If
5.12 Logical Expressions
5.13 Loops
5.14 Generators
5.15 Collections
5.16 General Branching
5.17 Never
Aldor is an expression-based language: every construct
in the language produces zero, one or more values. This chapter describes
the structure of expressions in Aldor and the rules for expression
evaluation.
5.1 : Names
The evaluation of a name in Aldor causes the value of a
variable or constant to be retrieved.
If a name refers to a variable or constant defined in the same scope or in
an enclosing scope then retrieving its value is a very inexpensive operation.
If a name refers to an imported constant, then there may be a cost associated
with the look up, depending on the degree of optimization used to compile the
program.
Consider the following example:
#include "aldor" f(n: SingleInteger): SingleInteger == if n < 2 then 1 else n * f(n-1);
In the last line, the name "n" is defined in the current scope
and the name "f" is defined in an enclosing scope, so their use
is very inexpensive. The names "*", "-" and "1" are
imported from SingleInteger
. If this program is compiled with
optimization, then there is no cost in using the values from
SingleInteger
. Without optimization, these values
would have to be dynamically retrieved from SingleInteger
at a modest cost.
A name may represent a variable, which may take on different values
at different points in a computation, or a constant, which
always refers to the same value every time it is used.
Names for constants may be overloaded in Aldor. That is,
it is possible to have more than one constant with a given name,
visible at the same point in a program. Names for variables cannot be
overloaded. A name cannot represent both a variable and a constant in
the same scope.
5.2 : Literals
Literal constants are expressions which represent the explicit data values
which appear in a program. There are three styles of literal constants
in Aldor: quoted strings, integers and floating-point numbers:
"Aloha!" -- quoted string literal 10203040 16rFFFFC010 -- integer literals 1.234e56 -- floating-point literal
The meaning of a constant in Aldor is determined by the environment
in which it is used. For example, the constant 1.234e56 might
be a value of type SingleFloat
, DoubleFloat
or Float
, depending on the context.
When a literal expression is encountered in a program, it is treated
as an application of a corresponding ``literal accepting''
operation:
string: Literal -> T integer: Literal -> T float: Literal -> T
(where T represents the type of the value being formed).
Each of these operations takes a single argument of type Literal
and constructs a value of the appropriate type. When programs are compiled
against the base Aldor library, the constant-folding optimization
will immediately convert constants of the following types to their machine
representations: String
, SingleInteger
, Integer
,
SingleFloat
, DoubleFloat
.
New types may provide their own interpretation of literal constants
by exporting a literal forming operation with the corresponding signature.
As a consequence, if operations for creating string literals, for example,
are available from several types, it may be necessary to provide a declaration
to indicate which kind of literal is intended. When implementing
literal-forming operations for new types, it is often useful to use the
string
literal-forming operation from String
.
Some types may accept some literal values and not others.
For example, a fixed-precision integer type might reject values
which lie outside the range of values representable by the type.
String literals
String-style literals are enclosed in quotation marks.
Inside a string-style literal, an underscore character "_" is used
as an escape character to modify the meaning of the characters which follow:
Examples:
"This literal is a _"string-style_" literal." "This literal contains a single underscore: '__'." s := "This literal is moderately long, and is broken _ over two lines, even though the result is a single line."When using
#include "aldor"
or #include "axiom"
,
the type String
provides string-style literals.
Integer literals
Integer-style literals provide a syntax for whole numbers.
These are in base ten unless otherwise indicated.
An integer-style literal is either
0
" or single "1
", or
r
", followed by a number of radix-digits using
digits and/or capital letters:
[0-9]+r[0-9A-Z]+.
In Aldor the numerals "0
" and "1
" are not
literal constants - they are treated as names so that
various mathematical structures which export 0 or 1 can do so,
without being required to support general integer constants.
An underscore appearing in the middle of an integer-style literal
is ignored together with any following white space.
An underscore which appears at the very beginning of a word
causes the whole word to be treated as an identifier, rather than
as a literal constant.
Examples:
22_394_547 38319238471239487123948237_ 192387491234712398478188_ 139823712983712938712391 _33 -- This word is an identifier, not an integer-style literal. 2r01010101010101010101 -- Base 2 16rDEADBEEF -- Base 16
When using #include "aldor"
, the types SingleInteger
and Integer
provide integer-style literals.
When using #include "axiom"
, the types SingleInteger
,
Integer
, NonNegativeInteger
and PositiveInteger
provide integer-style literals.
None of the above types support the baservalue
radix notation described above.
Floating-point literals
Float-style literals are numbers with a decimal point, an exponent, or both.
Examples:
3.0 3. 3e1 6.022E+23 0.2 .2 2e-1 4.8481E-6
An underscore appearing in the middle of a float-style literal
is ignored together with any following white space.
3.14159_26535_89793_23846_26433_83279_50288_41971_ 69399_37510_58209_74944_59230_78164_06286_20899_ 86280_34825_34211_70679_82148_08651_32823_06647
When using #include "aldor"
, the types SingleFloat
,
DoubleFloat
and Float
provide float-style literals.
When using #include "axiom"
, the types DoubleFloat
and Float
provide float-style literals.
None of the above types support the baservaluer
radix notation.
5.3 : Definitions
A constant in Aldor denotes a particular value which cannot be changed. The general syntax for a constant definition is:
x : T == E
x
is an identifier giving the name of the constant.
T
is an expression giving the type of the constant.
The type declaration is optional. If the type is declared, then the
type of E
must satisfy it. Otherwise the type is inferred
from the type of the expression E
.
E
is an expression which computes the value of the constant.
Examples:
a : Integer == 23; b == "Hello world!";
A function definition is a special case of a constant definition.
Function definitions are described in more detail in section 6.1.
Once a value has been given to a named constant, it cannot be
changed to refer to another value.
-- A constant cannot be changed to refer to a different value. num: Integer == 3; num: Integer == 4; -- In fact, it cannot be changed to refer to the same value! hi: String == "'Ello!"; hi: String == "'Ello!";
5.4 : Assignments
A variable in Aldor denotes a value which may change during the evaluation of a program. A variable is given a value by an assignment of the form:
x : T := E
x
is an identifier giving the name of the variable.
T
is an expression giving the type of the variable.
The type declaration is optional. If the type is declared, then the
type of E
must satisfy it. Otherwise the type is inferred
from the type of the expression E
.
E
is an expression which computes the value of the variable.
Several variables may be assigned a value at the same time:
(x1 : T1, ..., xn: Tn) := E
Any or all of the type declarations may be omitted,
in which case the ith variable would read "xi
",
rather than "xi: Ti
", and the type of xi
is
inferred from the type of the expression E
.
Examples:
n: Integer := 3; k := 3*n + 1; n := k quo 2; (a, b) := (1, 3); (s: String, x) := ("Natascia", false);
The value of an assignment expression is the same as the value of E
.
A special form of assignment expression is used to provide a general kind of
updating operation:
x(a1, ..., an) := E
Typically, x
is an expression which evaluates to a structured
data value, such as an array or a list, and the expressions ai
taken
together specify some component of x
. An assignment expression of
this form is treated as an application of the operation "set!
" of the
form:
set!(x, a1, ..., an, E)
For example, for lists, the "set!
" function (see
section 25.19) takes as its second (component specifying)
parameter either "first
" or "rest
", so we could have:
#include "aldor"; import from SingleInteger, List SingleInteger; L := [1,2,3]; ... L(first) := 4;
which would result in L
having the value list(4, 2, 3)
.
The value of this form is the return value of the function
"set!
".
5.5 : Functions
Function expressions are the primitive form for building functions in Aldor. An example of a function expression is:
(n: Integer, m: Integer): Integer +-> n * m + 1
See section 6.5 for a complete description.
5.6 : Functions calls
Typical expressions consist mostly of function calls. For example, consider the expression
l: List Integer := list(2 + 3, 4 - 5, 6 * 7, 8 ^ 9)
This has six explicit function calls (i.e. to the arithmetic functions
``+'', ``-'', ``*'', ``^'', to the n-ary function ``list'',
and to the type constructor function ``List''.
This expression also has eight implicit function calls to the literal
forming operation ``integer''.
See section 4.6 for a complete description
of the syntax of function calls.
5.7 : Imperatives
The do
expression evaluates E
and discards the computed value,
so that the do
expression returns no value.
do E
5.8 : Multiple values
A series of comma-separated expressions is used in Aldor to produce multiple values. The expressions are evaluated one by one, and the results are taken together as the (multiple) value of the whole expression:
3, 4, 5
This expression produces three values, all of type Integer.
In general, an expression in Aldor produces zero, one or more
values, each having its own type. For convenience,
nevertheless, we often speak of the value and the type
of an expression, even if it produces multiple values, when
the intended meaning is clear from the context.
See section 4.7 for a discussion of the use
of parentheses in comma expressions.
Functions may be declared to accept or return multiple values.
The example below shows how to declare, define and use a function which
involves multiple values.
#include "aldor" -- Declaring a function to accept and return multiple values. local f: (Integer, Integer) -> (Integer, Integer); -- Defining a function to accept and return multiple values. f(i: Integer, j: Integer): (Integer, Integer) == (i+j, i-j); -- Using a function which accepts and returns multiple values. (q: Integer, r: Integer) := divide f(100, 93); print << "The quotient is " << q << newline; print << "The remainder is " << r << newline;
The call to f
returns (193, 7)
,
which are passed as arguments to the function "divide
"
from Integer
. This returns
(193 quo 7, 193 rem 7)
which are assigned to q
and r
respectively.
Comma-separated expressions are not necessarily evaluated
in any particular order; furthermore, the evaluation of their
subexpression may be interleaved. Thus, the program:
#include "aldor" pr2(a: SingleInteger, b: SingleInteger): () == print << a << " " << b << newline; n: SingleInteger := 1; pr2({n := n + 1; n}, {n := n + 1; n})
could print any of "2 3
", "3 2
", "2 2
" or "3 3
".
Programs which depend on the order of evaluation of expressions to
be used as arguments to a function should use a sequence to make
the order explicit. (See section 5.9.)
5.9 : Sequences
A series of expressions to be evaluated one after another is called a sequence. A sequence is written as a semicolon-separated series of subexpressions:
a := 1; b := a + a; c := 3*b
The expressions (that is, the subexpressions of the sequence)
are evaluated one by one, in the order of their
occurrence, and the value of the last expression evaluated
is used as the value of the sequence. A sequence may also
contain one or more exit expressions, as described in
section5.10, which prevent the evaluation of any
expressions later in the sequence and so provide a way to return
a value other than that of the last expression in the sequence.
Because semicolon has a relatively low precedence,
it is usually necessary to enclose a sequence in braces ("{ }
")
or parentheses ("( )
") to get the desired result.
(See section 4.7 for details.)
Examples:
#include "aldor" import from SingleInteger; n1 := (a := 1; b := a + a; 3 * b); n2 := {a := 1; b := a + a; 3 * b} f(i0: SingleInteger): SingleInteger == { a := i0; b := a + a; 3 * b }
The meaning of a sequence is the same whether braces or parentheses are
used. Braces are normally used, especially to enclose a longer
expression split over several lines. Parentheses are occasionally used
to enclose shorter sequences as part of other expressions. An implicit
semicolon is assumed after a closing brace but not after a closing
parenthesis.
It is also possible to use indentation to construct sequences
by enclosing lines between the directives "#pile
" and "#endpile
".
In this context, a group of consecutive lines indented by the same amount
is called a pile and is treated as a sequence.
The precise rules for forming piles are described in section 24.3.
5.10 : Exits
An exit expression has the form:
condition => E
When an exit expression appears as one of the elements of a sequence,
the condition is evaluated. If the condition evaluates to true
then the value of the sequence is the value of the expression E
,
and no further components of the sequences are evaluated.
If the condition evaluates to false
then evaluation continues with
the next expression in the sequence. So the expression:
{ a; b; c => d; e; f }
is equivalent to
{ a; b; if c then d else { e; f } }
In a sequence which contains an exit expression, the type of the expression
E
must be compatible with the type of the sequence, that is, with the type of the final element of the sequence. If a sequence contains several
exit expressions, the types of the possible exit values must all be compatible.
If the condition is not of type Boolean
, then an implicit
application of the function test is performed to convert the
condition to the type Boolean
. (See section 11.2.)
An exit expression transfers control; it does not, itself, produce a
value.
As a result, the type of the exit expression is ()
, and so
an exit expression can only be used in a context which does not require
a value.
Examples:
#include "aldor" import from Integer; b: Integer := 1; a := { n := b * b; n < 10 => 0; n > 100 => 100 ; n } -- `a' will be assigned the value `0', corresponding to the -- right hand side of the first `=>'
Note that all of the exit values (i.e. 0 and 100)
have type Integer
, and the final element of the sequence
also has type Integer
.
A series of exit expressions is often a compact way to enumerate
a list of alternative cases:
#include "aldor" import from Integer; power(base: Integer, exp: Integer): Integer == { exp = 0 => 1; exp = 1 => base; exp = 2 => base * base; exp = 3 => base * base * base; val := 1; for i in 1..exp repeat val := val * base; val; } print << power(2, 0) << newline; -- Print `1' print << power(2, 1) << newline; -- Print `2' print << power(2, 2) << newline; -- Print `4' print << power(2, 3) << newline; -- Print `8' print << power(2, 9) << newline; -- Print `512'
Since any expression can appear after the =>, another sequence, which may contain other exit expressions, can appear there:
#include "aldor" import from Integer; +++ If `a' or `b' is zero, then return 0; +++ Otherwise return 1 if (a * b > 0), -1 elsewhere. productSign(a: Integer, b: Integer): Integer == { a = 0 => 0; b = 0 => 0; a < 0 => { b < 0 => 1; -1 } b > 0 => 1; -1 }
If an exit expression appears as a strict subexpression of an expression other than a sequence, the exit expression is treated as a sequence of length one:
if b < 0 then a > 0 => flag := false;
This example is treated as equivalent to:
if b < 0 then { a > 0 => flag := false }
5.11 : If
Conditional branching in Aldor is provided by the if expression.
if condition then T if condition then T else E
When an if
expression is evaluated, the condition
is evaluated.
If the condition evaluates to true
then the value of the if
expression is the value of the expression T
.
If the condition evaluates to false
and the else
clause
is present, then the value of the if
expression is the value
of the expression E
.
If the else
clause is not present, then the if
expression
returns no value.
An if expression used in a context which requires a value must
have both a then and an else branch. The types of the two
branches must be compatible, and the type of the expression is the
type which is satisfied by both branches.
In contexts which do not require a value,
the types of the then and else branch
are independent. In this case, the type of the if expression
is taken to be the type of the empty expression, and it is possible
to omit the else branch altogether.
If condition
is not of type Boolean
, then an implicit
application of the function test is performed to convert
condition
to type Boolean
. (See section 11.2)
Example:
#include "aldor" import from SingleInteger; foo(a: SingleInteger): SingleInteger == if a > 0 then a * a else 0
Note that if the else
clause is not present, then the if
expression cannot be used in a context which requires a value:
-- This assignment is not type correct. a : SingleInteger := if true then 1;
5.12 : Logical expressions
Logical expressions in Aldor are provided using the following forms:
E1 and E2 E1 or E2 not E
An "and
" expression is true
if both E1
and E2
are
true
. If the first expression evaluates to false
, then
the second expression is not evaluated.
An "or
" expression is true
if E1
or E2
or
both are true
. If the first expression evaluates to true
,
then the second expression is not evaluated.
A "not
" expression is true
if E
is false
.
If any of E1
, E2
or E
is not of type Boolean
,
then an implicit application of the function test is performed
to convert the value to type Boolean
. (See section 11.2)
The type of a logical expression is Boolean
.
Examples:
#include "aldor" if true and false then print << "This string will not be printed."; import from SingleInteger, String; -- Define `test' for SingleInteger: a null value returns false. test(x: SingleInteger): Boolean == if x = 0 then false else true; if 1 or 1 then print << "I can do this." << newline; -- Define `test' for String: an empty string returns false. -- Note that `#s' (`the length of s') uses -- the previous test for SingleInteger. test(s: String): Boolean == if #s then true else false; if true and 1 and "I can do this, too." then print << "This string will be printed."
The logical connectives "and
", "or
" and "not
" are
syntactic elements of the language that should not be confused with
similar functions exported from the type Boolean
(/\
, \/
and ~
). The functional versions from
Boolean
evaluate all of their arguments before computing their
result, and denote function values. The logical connectives cannot be
used as functions:
#include "aldor" import from List Boolean; -- This expression will print: `list(true, true, false)' print << map(~, [false, false, true]);
but you cannot say: map(not, [false, false, true])
,
since "not
" is not a function. (The Aldor function
"map
", defined for all aggregate types (see section 25.3 applies its first argument to each component of its second argument.)
5.13 : Loops
The Aldor language provdes a set of constructs to handle loops in a way which is both elegant and efficient. The concept lying at the base of Aldor loops is that of generator, discussed in depth in chapter9. Generators provide an abstract way to traverse values belonging to certain domains without violating the principle of encapsulation (see section 7.8). Generators can be considered as autonomous entities producing values. This section shows how values they produce can be used to control loops.
The general form of a loop expression is:
The iterators control how many times the body is evaluated. Any number of iterators may be used on a loop, and each is either a "while" or a "for" iterator.
A loop with no iterator repeats forever, unless terminated by an error or some other event. For example:
#include "aldor" -- This will repeat forever... repeat { print << "Row, row, row your boat," << newline; print << "Gently down the stream." << newline; print << "Merrily, merrily, merrily , merrily," << newline; print << "Life is but a dream." << newline; print << newline; }
Two forms, "break" and "iterate," may be used withing the loop body to control the loop evaluation, as described later on.
While-iterators
A "while" iterator, allows a loop to continue so long as a condition
remains true. The syntax of a "while" iterator is
while conditionIf a loop has a "while" iterator, then at the beginning of each iteration condition is evaluated. The result is then tested:
Just as with "if" and other expressions having tests for control flow, if the condition of a"while" is not a Boolean value, then an appropriate "test" function is applied to determine the sense of the condition.
The following example shows a repeat loop using a while iterator:
#include "aldor" import from Integer; n := 10000; k := 0; -- 'k' counts the number of iterations. while n ~= 1 repeat { k := k + 1; if odd? n then n := 3*n + 1 else n:= n quo 2; } print << "terminated after " << k << " iterations." << newline;
This loop counts the number of times the body has to be evaluated in order for n to reach one. When n reaches 1, the evaluation of the loop is terminated, and the count is printed. (It is a well-known conjecture that this process will terminate for all integer n > 0. This is sometimes called the "3n + 1 problem".)
In a case such as this it is important to give k an initial value, since the loop may be iterated zero times! In fact, if the while condition is false the first time that it is evaluated, then the repeat body will not be executed at all:
#include "aldor" import from Integer; n := 0; while n > 0 repeat { -- This will never be executed. print << "Hello world!" << newline; }
For-iterators
Very often, loops are used to traverse certain kinds of data structures, such as lists, arrays or tables, or to execute some operations for all the numerical values in a defined range.
"for" iterators make this sort of loop convenient to write. Take, for instance, the following examples:
-- Add up the elements of a list, return the sum. sum(ls: List Integer): Integer == { n := 0; for i in ls repeat n := n + i; n } -- Add up the elements of an array, return the sum. sum(arr: Array Integer): Integer == { n := 0; for i in arr repeat n := n + i; n } -- Add up the odd numbers in a range, return the sum. sum(lo: Integer, hi: Integer): Integer == { n := 0; if even? lo then lo := lo + 1; for i in lo..hi by 2 repeat n := n + i; n } import from List Integer; -- Use 'sum: List Integer -> Integer': print << sum([1,2,3,4]) -- Prints '10'.
The "for" iterators in these examples all have the form
for lhs in Expr
The lhs part specifies a varable which will take on the successive values over the course of the iteration. The lhs has the form
[free] name [: Type]
The type declaration is optional. If it is missing, the type of the variable is inferred from the source of the values, Expr
The "free" part of the lhs determines the scope of the variable. Without it, a new variable of the given name is made local to the loop. If the word "free" is present, then the loop uses an existing variable from the context. In this case, the value of the variable is available after the loop terminates.
The example above can be rewritten useing a free loop variable:
# include "aldor" import from Integer; n := 10000; k := 0 -- Make a top-level variable 'k'. for free k in 1.. repeat { -- Use above 'k' freely in the loop. if odd? n then n := 3*n + 1 else n := n quo 2; if n = 1 then break; -- exit the loop if n = 1 } -- Here the last value of 'k' is available. print << "terminated after " << k << " iterations." << newline;
The previous example uses the "break" construct explained later in this section. When a break is evaluated it causes the termination of the loop. In this case, the break is the only exit point of the loop, because we are iterating over an open segment, producing infinitely many values: 1, 2, 3, ...
Now we turn our attention to the expression traversed by the "for" iterator. In the examples, we have seen
In general, the "for" iterator expression must have type Generator T, where T is the type of the "for" variable. If the expression is not of this type, then an implicit type will be used. See chapter 9 for a description of generators and how to create new ones.
The examples we have seen work because the list, array and segment types provided by "libaldor" export generator functions. Other examples of generators provided by "libaldor" are "step", provided by SingleFloat, DoubleFloat and Float, which creates uniformly spaced points on a floting point interval, and "tails" which provides successive tails of a list.
There is a second form of "for" iterator which filters the values used. This has the form:
for lhs in Expr | condition
This kind of "for" iterator skips those values which do not satisfy the condition. For example, the sum example we saw earlier could have been wrtten as:
#include "aldor" -- Add up the odd numbers in a range sum(lo: Integer, hi: Integer): Integer == } n := 0; for i in lo..hi | odd? i repeat n := n + 1; n }
Multiple interators
A "repeat" loop may have any number of iterators, with the following syntax:
iterator1 ... iteratorn repeat Body
The loop is repreated until one of the iterators terminates it: the first "while" which has false condition or the first "for" which consumes all its values ends the loop.
This is convenient when the termination condition does not relate directly to a "for" variable, or when structures are to be traversed in parallel.
Continuing the example from earlier, we may use a "while" to decide if the end has been reached, and, at the same time, use a "for" to count the number of times we have evaluated the loop body:
#include "aldor" import from Integer; n := 10000; k := 0; while n ~= 1 for free k in 1.. repeat if odd? n then n := 3*n + 1 else n := n quo 2; print << "Terminated after " << k << " iterations." << newline;
Multiple "for" iterators can allow lists to be combined in an efficient way:
#include "aldor" import from List Integer; l1 := [1,2,3]; l2 := [8,7,6,5]; -- Add the pair-wise products in two lists. x := 0; for n1 in l1 for n2 in l2 repeat x := x + n1 * n2 print << "The result is " << x << newline
These two "for" iterators are used in parallel, like a zipper combining the two lists. The loop stops at the end of the shortest list, in this case giving a sum of three products.
This is not a double loop - to use all pairs with the number from l1 and the second number from ls, you would use two nested loops, each with its own "repeat":
#include "aldor" import from List Integer; l1 : [1,2,3]; l2 : [8,7,6,5]; x := 0; for n1 in l1 repreat for n2 in l2 repeat x := x + n1 * n2; print << "The result is " << x << newline
Using more than one iterator is often the most efficient natural way to write a loop. A loop with two "for" iterators is more efficient than
... m: SingleInteger := min(#l1, #l2); for i in 1..m repeat x := x + l1.i * l2.i
because it does not need to traverse the lists each time to pick off the desired elements. And the code is more concise than
... t1 := l1; t2 := l2; while (not empty? t1 and not empty? t2) repeat { x := x + first t1 * first t2; t1 := rest t1; t2 := rest t2; }
Evaluating a "break" causes a loop to terminate. For example:
#include "aldor" import from Integer; n := 10000; k := 0; -- 'k' counts the number of iterations. repeat { if odd? n then n := 3*n + 1 else n := n quo 2; k := k + 1; if n = 1 then break; } print << "Terminated after " << k << " iterations." << newline
"break" is most useful when the condition which quits the loop falls most naturally in the middle or at the end of the loop body. Sometimes it is possible to change a test which appears at the end to a test at the begining. This helps make programs more readable, since one sees immediately what will cause the loop to end. Also, it allows the exit to be controlled by a "while" iterator, rather than an "if" and a "break".
Iterate
Evaluating an "iterate" abandons the current evaluation of the loop body and starts the next iteration. For example:
#include "aldor" import from Integer; n := 10000; k := 0; -- 'k' counts the number of iterations. repeat { k := k + 1; if odd? n then { n := 3*n + 1; iterate } n := n quo 2; if n = 1 then break } print << "Terminated after " << k << " iterations." << newline;
This example does the same thing as the previous one, but is organized slightly differently. The "iterate" on the second line of the loop body causes the rest of the body to be skipped.
"iterate" can be used instread of placing the remainder of a loop body inside an "if" expression. This can make programs easier to read, by emphasizing that certain conditions are not expected, and by avoiding extra levels of indentation. It is particularly useful when the decision to go on to the next iteration of a loop is buried deep inside some other logic, rather than appearing at the top level of the loop body.
An "iterate" is equivalent to a "goto" branching to the end of the loop body. Thus, the meaning of "iterate" is independent of whether there are any "while" or "for" iterators controlling the loop.
If an "iterate" occurs inside nested loops, it steps the deepest one.
Definition in low-level terms
It is possible to express the loop behavious in terms of gotos and labels. A loop of the form
it1 it2 ... itn repeat Body
is equivalent to
{ init1; init2; ... initn; @TOP step1; step2; ... stepn; Body; goto TOP; @DONE }
Where, if iti is "while condi", then initi is empty and stepi is
if not condi then goto DONE
if iti is "for lhsi in expri | condi", then initi is
gi := generator expri;
and stepi is
step! gi; if empty? gi then goto DONE lhsi := value gi if not condi then goto TOP
(a "for" without a condition is treated as if the condition were "true").
In this, TOP, DONE and the gi are generated names, and are not accessible to the other parts of the program.
5.14 : Generate expressions
Generate expressions are used to create generators.
For a complete description of how to use generate to create
a generator, see chapter 9. The general syntax for a
generate expression is:
generate E generate to n of E
E is an expression which represents code which will be evaluated each time a value is extracted from the generator. Evaluation begins at the start of the expression E and continues until an expression of the form
yield v
is processed, where v is the value to be passed back to the
context which is collecting values from the generator. Each time a
value is requested after the first value is yielded, control resumes
after the yield expression which produced the previous value.
5.15 : Collections
A collect expression provides a convenient shorthand for
creating generators and aggregate objects. A collect expression
has the form:
E iter1 ... itern
where n >= 1. The iteri are iterators, as described in
section 5.13. Consider the following example:
#include "aldor" ... import from Integer, List Integer; print << [x*x for x in 1..10] << newline; print << [x*y for x in 1..10 for y in 10..1 by -1];
This program creates a list of the squares of the integers from 1 to 10,
and then a list of products of integers. Note that while can form
an iterator, and can therefore be used in a collect expression.
Note that the square brackets are not part of the collect expression,
but are simply a shorthand for a call to the function bracket,
with the value of the collection as an argument. The domain
List Integer from the Aldor base library exports a
function with the signature
bracket: Generator Integer -> %
, which is called twice in the
above example.
As collect expressions produce generators, one would expect that
generate expressions and collect expressions are related. The collect
expression:
Thus, x*y for x in 1..9 for y in 9..1 by -1 is the same as:
generate for x in 1..9 for y in 9..1 by -1 repeat yield x*y
Collect expressions provide a convenient notation for creating new aggregates,
and require no additional functionality in the language.
5.16 : General branching
Aldor provides unconditional branching using label expressions and goto expressions. A label expression is of the following form:
@L E
where L
is an identifier known as the label name,
and E
is any expression. The type of the label expression
is the same as the type of E
. Names which are used as labels
have no relationship with variables or constants of the same name,
and a label name may also be used as a variable or constant. A label
name obeys the same scope rules as constants or variables, but may not
appear in any expressions other than gotos and other label
expressions. Since labels are constants, it is an error to bind the
same label twice in the same scope.
A goto expression has the following form:
goto L
where L
is the name of a label.
After the evaluation of a goto expression, execution resumes
with the expression associated with the label L
.
The type of the goto expression itself is the type Exit
.
The label must appear in the same function body as the goto
. In
addition, it is an error for a goto
to branch into an inner scope
of the scope in which it appears (that is,
a local function or any repeat or
collection including a for, where, add or with
expression). A goto
may also not branch out of a function or a
with or add expression.
Example:
foo(a: SingleInteger): SingleInteger == { if a <= 0 then goto ERROR; return a * a; @ERROR print << "The argument must be a positive value!" << newline; 0 }
If the first test is successful, then the return expression is skipped and the execution proceeds on the line following ERROR. Labels can also be defined at the top level of a file, since the top level of a file is treated as a sequence:
#include "aldor" @LAB1 print << "You will see this forever..." << newline; goto LAB1;
5.17 : Never
The expression never is a special value, of type Exit
,
which acts as a programmer-supplied assertion that execution will never
reach that point. An exit expression can be useful
as it may allow a program to be translated into more efficient code.
The following Aldor code is a possible use of never
s := { x = 0 => "zero"; x > 0 => "positive"; x < 0 => "negative"; -- This expression is unreachable never }
With luck, the never at the end of the sequence will not be
reached in any execution of the program. (If it is reached, Aldor
will complain "User error: Reached a "never"").