Procedural Programming


This section shows you how to use the procedural features of the Analytica modeling language, including:

  • Begin-End, (), and “;” for grouping expressions
  • Declaring local variables and assigning to them
  • For and While loops and recursion
  • Local indexes
  • References and data structures
  • Handles to objects
  • Dialog functions
  • Miscellaneous functions


A procedural program is list of instructions to a computer. Each instruction tells the computer what to do, or it might change the sequence to execute the instructions. Most Analytica models are non-procedural — that is, they consist of an unsequenced set of definitions of variables. Each definition is a simple expression that contain functions, operators, constants, and other variables, but no procedural constructs controlling the sequence of execution. In this way, Analytica is like a standard spreadsheet application, in which each cell contains a simple formula with no procedural constructs. Analytica selects the sequence in which to evaluate variables based on the dependencies among them, somewhat in the same way spreadsheets determine the sequence to evaluate their cells. Controlling the evaluation sequence via conditional statements and loops is a large part of programming in a language like in Fortran, Visual Basic, or C++. Non-procedural languages like Analytica free you from having to worry about sequencing. Non-procedural models or programs are usually much easier to write and understand than procedural programs because you can understand each definition (or formula) without worrying about the sequence of execution.

However, procedural languages enable you to write more powerful functions that are hard or impossible without their procedural constructs. For this reason, Analytica offers a set of programming constructs, described in this chapter, providing a general procedural programming language for those who need it.

You can use these constructs to control the flow of execution only within the definition of a variable or function. Evaluating one variable or function cannot (usually) change the value of another variables or functions. Thus, these procedural constructs do not affect the simple non-procedural relationship among variables and functions. The only exception is that a function called from an event handler such as OnChange or a button OnClick attribute can change the definition of a global variable (see Button Creation for more details).

Sections

An example of procedural programming

The following function, Factors(), computes the prime factors of an integer x. It illustrates many of the key constructs of procedural programming.

Example1.png

See below for an explanation of each of these constructs, and cross references to where they are.

Numbers identify

features below

Function Factors(x)

Definition:

1 VAR result := [1];
2 VAR n := 2;
3 WHILE n <= x DO
4 BEGIN
2 VAR r := Floor(x/n);

IF r*n = x THEN

5 (result := Concat(result, [n]);
6 x := r)

ELSE n := n + 1

4,7 END; /* End While loop */
7, 8 result /* End Definition */

This definition illustrates these features:

  1. VAR x := e construct defines a local variable x, and sets an initial value e. See “Defining a local variable: Var v := e” on page 376 for more.
  2. You can group several expressions (statements) into a definition by separating them by “;” (semicolons). Expressions can be on the same line or successive lines. See “Begin-End, (), and “;” for grouping expressions” on page 376.
  3. While test Do body construct tests condition Test, and, if True, evaluates Body, and repeats until condition Test is False. See “While(Test) Do Body” on page 381.
  4. Begin e1; e2; … End groups several expressions separated by semicolons “;” — in this case as the body of a While loop. See “Begin-End, (), and “;” for grouping expressions” on page 376.
  5. (e1; e2; …) is another way to group expressions — in this case, as the action to be taken in the Then case. See “Begin-End, (), and “;” for grouping expressions” on page 376.
  6. x := e lets you assign the value of an expression e to a local variable x or, as in the first case, to a parameter of a function. See “Assigning to a local variable: v := e” on page 377.
  7. A comment is enclosed between /* and */ as an alternative to { and }.
  8. A group of expressions returns the value of the last expression — here the function Factors returns the value of result — whether the group is delimited by Begin and End, by parentheses marks ( and ), or, as here, by nothing.

Summary of programming constructs

Construct Meaning: For more, see
e1; e2; … ei Semicolons join a group of expressions to be evaluated in sequence
BEGIN e1; e2; …

ei END

A group of expressions to be evaluated in sequence
(e1; e2; … ei) Another way to group expressions
m .. n Generates a list of successive integers from m to n
Var x := e Define local variable x and assign initial value e
Index i := e Define local index i and assign initial value e
x := e Assigns value from evaluating e to local variable x.

Returns value e.

While Test Do Body While Test is True, evaluate Body and repeat.

Returns last value of Body.

{ comments }

/* comments */

Curly brackets { } and /* */ are alternative ways to enclose comments to be ignored by the parser.
'text'

"text"

You can use single or double quotes to enclose a literal text value, but they must match.
For x := a DO e Assigns to loop variable x, successive atoms from array a and repeats evaluation expression e for each value of x.

Returns an array of values of e with the same indexes as a.

For x[i, j…] := a DO e Same, but it assigns to x successive sub-arrays of a, each indexed by the indices, [i, j …
\ e Creates a reference to the value of expression e.
1 VAR result := [1];
\ [i, j …] e Creates an array indexed by any indexes of e other than i, j … of references to sub-arrays of e each indexed by i, j ….
# r Returns the value referred to by reference r.

Begin-End, (), and “;” for grouping expressions

As illustrated above, you can group several expressions (statements) as the definition of a variable or function simply by separating them by semicolons (;). To group several expressions as a condition or action of If a Then b Else c or While a Do b, or, indeed, anywhere a single expression is valid, you should enclose the expressions between Begin and End, or between parentheses characters ( and ).

The overall value of the group of statements is the value from evaluating the last expression. For example:

(VAR x := 10; x := x/2; x - 2) → 3

Analytica also tolerates a semicolon (;) after the last expression in a group. It still returns the value of the last expression. For example:

(VAR x := 10; x := x/2; x/2;) → 2.5

The statements can be grouped on one line, or over several lines. In fact, Analytica does not care where new-lines, spaces, or tabs occur within an expression or sequence of expressions — as long as they are not within a number or identifier.

Declaring local variables and assigning to them

Defining a local variable:

This construct creates a local variable v and initializes it with the value from evaluating expression e. You can then use v in subsequent expressions within this context — that is, in following expressions in this group, or nested within expressions in this group. You cannot refer to a local variable outside its context — for example, in the definition of another variable or function.

If v has the same identifier (name) as a global variable, any subsequent mention of v in this context refers to the just-defined local variable, not the global.

Examples

Instead of defining a variable as:

Sum(Array_a*Array_b, N)/(1+Sum(Array_a*Array_b, N))

Define it as:

VAR t := Sum(Array_a*Array*b, N); t/(1+t)

To compute a correlation between Xdata and Ydata, instead of:

Sum((Xdata-Sum(Xdata, Data_index)/Nopts)*(Ydata-

Sum(Ydata, Data_index)/Nopts), Data_index)/

Sqrt(Sum((Xdata-Sum(Xdata, Data_index)/

Nopts)^2, Data_index) * Sum((Ydata -

Sum(Ydata, Data_index)/Nopts)^2, Data_index))

Define the correlation as:

VAR mx := Sum(Xdata, Data_index)/Nopts;

VAR my := Sum(Ydata, Data_index)/Nopts;

VAR dx := Xdata - mx;

VAR dy := Ydata - my;

Sum(dx*dy, Data_index)/Sqrt(Sum(dx^2, Data_index)*Sum(dy^2, Data_index))

The latter expression is faster to execute and easier to read. The correlation expression in this example is an alternative to Analytica’s built-in Correlation() function when data is dimensioned by an index other than the system index Run.

Assigning to a local variable:

The := (assignment operator) sets the local variable v to the value of expression e.

The assignment expression also returns the value of e, although it is usually the effect of the assignment that is of primary interest.

The equal sign = does not do assignment. It tests for equality between two values.

Within the definition of a function, you can also assign a new value to any parameter. This only changes the parameter and does not affect any global variables used as actual parameters in the call to the function.

Tip
Usually, you cannot assign to a global variable — that is, to a variable created as a diagram node. You can assign only to a local variable, declared in this definition using Var or Index, in the current context — that is, at the same or enclosing level in this definition. In a function definition, you can also assign to a parameter.This prevents side effects — i.e., where evaluating a global variable or function changes a global variable, other than one that mentions this variable or function in its definition. Analytica’s lack of side effects makes models much easier to write, understand, and debug than normal computer languages that allow side effects. You can tell how a variable is computed just by looking at its definition, without having to worry about parts of the model not mentioned in the definition. There are a few exceptions to this rule of no assignments to globals: You can assign to globals in button scripts or functions called from button scripts. See Button Creation for details. You can also assign to a global variable V from the definition of X when V is defined as ComputedBy(X).

ComputedBy(x)

This function indicates that the value of a variable is computed as a side-effect of another variable, x. Suppose v is defined as ComputedBy(x), and the value of v needs to be computed, then Analytica will evaluate x. During the evaluation of x, x must set the value of v using an assignment operator.

Even though v is a side-effect of x, its definition is still referentially transparent, which means that its definition completely describes its computed value.

ComputedBy is useful when multiple items are computed simultaneously within an expression. It is particularly useful from within an Iterate() function when several variables need to be updated in each iteration.

Variable rot := ... {a 2-D rotation matrix indexed by Dim and Dim2}

Variable X_rot := ComputedBy(Y_rot)

Variable Y_rot :=

BEGIN

Var v := Array(Dim,[X,Y]);

Var v_r := sum( rot*v, Dim );

X_rot := v_r[Dim2='x'];

v_r[Dim2='y'];

END

Assigning to a slice of a local variable

Slice assignment means assigning a value into an element or slice of an array contained by a local variable, for example:

x[i = n] := e

x must be a local variable, i is an index (local or global), n evaluates to be a value or values of i, and e is any expression. If x was not array or was an array not indexed by i, the slice assignment adds i as a dimension of x. The result returned from the assignment operator is the value e, not the full value of x, which can be a source of confusion but is that way so you can chain assignments, e.g.:

x[i=3] := x[i=5] := 7

You can write some algorithms much more easily and efficiently using slice assignment. For example:

Function Fibonacci_series(f1, f2, n: Number Atom) :=

INDEX m := 1..n;

VAR result := 0;

result[m = 1] := f1;

result[m = 2] := f2;

FOR I := 3..n DO result[m = i] := result[m = i -1] + result[m = i - 2];

result

In the first slice assignment:

result[m = 1] := f1;

result was not previously indexed by m. So the assignment adds the index m to result, making it into an array with value f1 for m=1 and its original value, 0, for all other values of m.

More generally, in a slice assignment:

x[i = n] := e

If x was already indexed by i, it sets x[i=n] to the value of e. All other slices of x over i retain their previous values. If x was indexed by other indexes, say j, the result is indexed by i and j. The assigned slice x[i=n] has the value e for all values of the other index(es) j.

You can index by position as well as name in a slice assignment, for example:

x[@i = 2] := e

This assigns the value of e as the second slice of x over index i.

To set a cell in a multi-dimensional array, include multiple subscript coordinates, e.g.:

x[i=2,j=5,k=3] := 7

Slice assignment array abstracts when x, n, or e have extra dimensions, and the abstraction is coordinated when an index is shared by x, n, or e. Using abstraction, it is possible to assign to many cells in a single assignment operation.

For and While loops and recursion

Tip
Analytica’s Intelligent Array features means that you rarely need explicit iteration using For loops to repeat operations over each dimensions of an array, often used in conventional computer language. If you find yourself using For loops a lot in Analytica, this might be a sign that you are not using Intelligent Arrays effectively. If so, please (re)read the sections on Intelligent Arrays.

For i := a Do expr

The For loop successively assigns the next atom from array a to local index i, and evaluates expression expr. expr might refer to i, for xample to slice out a particular element of an array. a might be a list of values defined by m..n or Sequence(m, n, dx) or it might be a multidimensional array. Normally, it evaluates the body expr once for each atom in a.

The result of the For loop is an array with all the indexes of a containing the values of each evaluation of expr. If any or all evaluations of expr have any additional index(es), they are also indexes of the result.

Usually, the Intelligent Array features take care of iterating over indexes of arrays without the need for explicit looping. A For loop is sometimes useful in these specialized cases:

  • To avoid selected evaluations of expr that might be invalid or out of range, and can be prevented by nesting an If-Then-Else inside a For.
  • To apply an Analytica function that requires an atom or one- or two-dimensional array input to a higher-dimensioned array.
  • To reduce the memory needed f or calculations with very large arrays by reducing the memory requirement for intermediate results.

See below for an example of each of these three cases.

Library Special

Avoiding out-of-range errors

Consider the following expression:

If x<0 Then 0 Else Sqrt(x)

The If-Then-Else is included in this expression to avoid the warning “Square root of a negative number.” However, if x is an array of values, this expression cannot avoid the warning since Sqrt(x) is evaluated before If-Then-Else selects which elements of Sqrt(x) to include. To avoid the warning (assuming x is indexed by i), the expression can be rewritten as:

For j:=I do

If x[i=j]<0 then 0 else Sqrt(x[i=j])

Or as (see next section):

Using y:=x in i do

If y<0 Then 0 else Sqrt(y)

Situations like this can often occur during slicing operations. For example, to shift x one position to the right along i, the following expression would encounter an error:

if i<2 then x[i=1] else x[i=i-1]

The error occurs when x[i=i-1] is evaluated since the value corresponding to i-1=0 is out of range. The avoid the error, the expression can be rewritten as:

For j:=i do If j<2 then x[i=1] else x[i=j-1]

Out-of-range errors can also be avoided without using For by placing the conditional inside an argument. For example, the two examples above can be written without For as follows:

Sqrt(if x<0 then 0 else x)

x[i=(if i<2 then 1 else i-1)]

Dimensionality reduction

For can be used to apply a function that requires an atom, one- or two- dimensional input to a multi-dimensional result. This usage is rare in Analytica since array abstraction normally does this automatically; however, the need occasionally arises in some circumstances.

Suppose you have an array A indexed by I, and you wish to apply a function f(x) to each element of A along I. In a conventional programming language, this would require a loop over the elements of A; however, in almost all cases, Analytica’s array abstraction does this automatically — the expression is simply f(A), and the result remains indexed by i. However, there are a few cases where Analytica does not automatically array abstract, or it is possible to write a user-defined function that does not automatically array abstract (e.g., by declaring a parameter to be of type Atom, as detailed in Parameter Qualifiers). For example, Analytica does not array abstract over functions such as Sequence, Split, Subset, or Unique, since these return un-indexed lists of varying lengths that are unknown until the function evaluates. Suppose we have the following variables defined (note that A is an array of text values):

A: Index_1 ▼
1
1 A, B, C
2 D, E, F
3 G, H, I
Comments


Lchrisman

29 months ago
Score 0
It says you can use these only within the definition of a variable or function, but in fact you can use procedural code in the OnClick of a button, which is a place where procedural style is often most likely to be useful.

You are not allowed to post comments.