Assignment Operator :=
An assignment with syntax
v := expr
sets the value of variable v to the value obtained from expression expr.
Analytica improves transparency by banning assignments
Assignment is one of the most common operations in most computer languages. But, it destroys code transparency by creating "side effects": If an expression used to calculate variable A assigns a new value to Variable B, evaluating A has the side effect of changing B, which may be in a quite different part of the code. Such a side effect means that you can't tell for sure how B is computed just by looking at its definition, or more generally, the local code. To maintain transparency, Analytica's designers ban assignments (except in special cases). This takes a bit of getting used to for experienced programmers. But, you will soon discover the huge advantage of improved transparency.
Analytica allows assignment in special cases
Analytica does allow assignments in these special cases that do not unduly damage this transparency:
- You may assign to a local variable v declared in the same definition using VAR, Metavar, or Alias, or where v is a parameter of the function. The assignment affects only a local variable and so does not affect transparency.
- You may assign to a Global variable in an OnClick attribute of a Button or OnChange attribute of an input variable. The rationale is the the user must actively click the Button or change the Input Variable, and so changing the definition of a variable is OK. (Assignment is also allowed in a Script attribute, but this is obsolete).
- You may assign to a Global variable a user-defined Function called from an OnClick or OnChange attribute (or in a Function called from such a Function, and so on.)
- You may assign to an Attribute of a Global variable in the same contexts that you may assign to the value of the variable.
- You may assign to a Global Variable A in the Definition of a Global Variable B if A is defined as ComputedBy(B). This is useful if A has an algorithm that computes two (or more) results that you want to retain -- as the value of A and B. It does not damage transparency since you can immediately see that B is computed in the definition of A, so you know where to look.
- You may assign a value to the RandomSeed system variable.
Assignment to a Local Variable
This example shows the use of a local variable in an expression:
VAR count := 0; FOR j:=I DO ( count := count + (A[I=j] <> Null) ); count
This expression adds up the elements of A along I that are not equal to NULL. Note that if A has dimensions other than I, this expression works fine, and count will contain these other indexes. To create flexible expressions, you should think about whether your use of assignment will generalize as new dimensions are added.
Assignment in OnClick or OnChange
You may assign to a Global variable in an OnClick attribute of a Button or OnChange attribute of an input variable. You may also assign to a Global Variable in a function called from an OnClick or OnChange attribute, or a function called from such function, and so on. The rationale is that by clicking the Button or changing the input variable, the user is deliberately making a change, which may reasonably change the value and definition of a Global Variable.
Assignment to a Global variable using OnClick or OnChange can be done as follows:
Va1 := expr
You may also assign in a Script attribute, but that is a legacy feature, which been replaced by OnClick and OnChange attributes. When called directly from the script attribute of a button or pict object, it is necessary to surround an assignment operation in parentheses, such as:
( Va1 := expr )
Without parentheses, a script will interpret the expression as typescript rather than as an expression. When evaluated in typescript, the right-hand side is not evaluated, so the definition of Va1 would be set literally to the character-for-character expression written on the right-hand side. The parentheses cause typescript to interpret the line as an expression.
To avoid this subtlety, we often recommend creating any complex button-script logic in a user-defined function, where the logic resides in a definition using the syntax most Analytica users are already well-accustomed to. Your button script can then consist of a simple single call to your user-defined function.
When assigning to a global object, the right-hand side is evaluated, and the definition of the object set to the literal value if atomic, or to an edit table containing the literal values that were computed. There are many uses for this, not covered here. Assignment in this fashion should not be used when the result of evaluating the right-hand side is dimensioned by a local index. (Edit tables cannot be defined over local indexes). If you have a local index, you should set up a global index first, and redimension the result before assigning it to the variable.
Assignment to an Attribute
The assignment operator can also be used to assign to any attribute of a global object when used directly or indirectly from a button script. Here the syntax is
attrib of obj := expr
An example is
description of X := TextSentenceCase( TextReplace( Template, "%1%", "X") )
The right-hand side is evaluated, and the attribute is set to the value computed.
When used directly from a script, again parentheses are required, so that it is interpreted as an expression.
The only way to remove an attribute value from a script is using:
attrib of X := Undefined
The related expression
attrib of X := Null
sets the attribute value to Null, which is a legitimate attribute value and does not have same effect within Analytica (internally these have different meanings). However, from an Analytica expression, it is not possible to differentiate between an attribute value that does not exist and an attribute value set to Null. (While this distinction was possible prior to 4.0, it has been removed to simplify things for users). This method for removing an attribute value is the only instance where a user may need to be aware of the distinction between Undefined and Null.
Assigning a value or definition
Note that the following two expressions are not equivalent:
X := "B" definition of X := "B"
In the first case, the definition of X will contain the quotation characters, and when evaluated, X will be the text value "B". In the second case, the definition of X will not contain quotation characters, and when evaluated, it will return the result of evaluating the variable B. The following table highlights the subtle differences between assigning values and definitions:
Expression | Resulting Definition of X |
---|---|
X := 1+2 | 3 |
X := "1+2" | "1+2" |
Definition of X := 1+2 | 3 |
Definition of X := "1+2" | 1+2 |
Assignment to RandomSeed
When Analytica uses pseudo-random numbers, such as when sampling from a distribution, the actual sample generated is based on the current RandomSeed, used by the internal random number generators. If you evaluate an uncertain variable at different times, or chance variables in different orders, you are likely to get different samples.
To reproduce the same sample each time, one method that can be used is to reset the random seed to a known value prior to calling the distribution function. For example,
RandomSeed := 999; Normal(0,1)
This would generate the same sample for the normal distribution same every time it is evaluated. Analytica allows an assignment to the RandomSeed system variable from any expression, and doing so from a variable or function definition does not cause previously computed samples or results to be invalidated. However, this is the only global object that can be assigned to while a variable is being evaluated.
Gotchas
Interactions of Assignment with Array Abstraction
Analytica performs array-based operations. When an operation has side-effects, as the assignment operator does, this may have some counter-intuitive results. For example, compare the following three expressions, all of which may seem the same to someone accustomed to a procedural programming language, but for which only (C) produces the expected result
(A) var result := Uniform(0,1); if result<0.5 then result := 0.5; result
(B) var result := Uniform(0,1); if result<0.5 then result:=0.5 else result:=result
(C) var result := Uniform(0,1); result := if result<0.5 then 0.5 else result
To understand this foible, you must realize that Analytica evaluates the IF-THEN-ELSE in an array fashion, evaluating the entire IF part, then evaluating the THEN part in an array-operation only once, and evaluating the ELSE part in an array fashion only once. It is not iterating over each atomic element of result. (We sometimes refer to this distinction has "vertical" vs. "horizontal" abstraction).
In case (A), when result<0.5 is evaluated (assuming Sample mode), at least one element of result is likely to satisfy result<0.5, so the THEN clause will be evaluated. When this happens, the local variable is set to the scalar value of 0.5 -- it is no longer indexed by Run. Probably not what the author expected.
In case (B) result<0.5 will have some true and some false instances, so both the Then and Else clauses will be evaluated. When Then is evaluated, the entire value becomes 0.5 as the assignment side-effect, and the result:=result part has no real impact. Again, not what the author expected.
Case (C) does work as expected. Here the if-then-else is evalutaed as an array operation, returning the correct truncating, and the assignment of the entire array occurs once.
As a general rule, to avoid this foible, you should avoid using assignment from within a conditional THEN or ELSE clause. There are situations where you can legitimately do so, but when doing so, you should ensure that your antecedent (the IF condition) is guaranteed to be a scalar at all times.
Don't change a global index used by a transient array
You should never write a function that changes a global index (assign a new value) that might be used by a transient array. A transient array exists only temporarily during a computation, such as an array value of a local variables, a function parameter, or an intermediate result during evaluation of an expression. If you modify a Global Index with an assignment, any transient arrays using that index may be corrupted, with unpredictable consequences, including incorrect results, or a crash. Analytica does not detect this situation (which would be computationally expensive). Such are the perils of side-effects! For example, suppose J
is a global index, and this is in a function called by a Script:
Var x := Array(J, [1, 2, 3]); J := Concat(J, ['D']); F(x)
The assignment changing J
makes x
inconsistent. Global tables that use J
use spliced to keep them consistent, but there is no direct link from J
to the transient array in local variable x
, so it cannot be made consistent.
Bottom line: Never change a global index while it is in-use by a value in a local variable.
Note: It is okay to assign a new value to a local index. When you do to, it essentially creates a new local index, treated as separate from the one held by local variables.
Here is an example of a transient value that doesn't involve a local variable or parameter:
Index I := 1..10 Function F() Definition: (I+1)/(I := 10..100)
(I+1)
is a transient array indexed by I
, which is held in memory as the denominator is computed. The computation of the denominator changes I
-- including its length. The result is inconsisten.
Slice/Subscript Assignment
(new to 4.0)
When v is a local variable, you may assign to a single slice of a value, leaving all other current values of the local variable unchanged. The syntax for this is:
v[I=x] := y v[@I=n] := x
This is only permitted when v is a local variable.
If you wish to change a single slice of a global variable, X, from a button script, and if you can guarantee that every cell of the global variable contains a literal value, you can accomplish this using:
var v := Va1; v[I=x] := y; Va1 := v
You could also accomplish this equivalently using:
Va1 := if I=x then y else Va1
For a single slice, the latter is equally efficient; however, if you have a complex algorithm that will manipulate many slices and perform many slice assignments in the process, direct assignment to slices prior to writing the value back to the global value is substantially more efficient.
Additional information on Slice Assignment is available at Subscript/Slice Operator.
Assignment to Tables
(new to Analytica 4.5) When you set a global variable to an array (which you can only do from a button script or a UDF that is called from a button script), if the target variable is currently a Table, DetermTable, ProbTable or IntraTable, then the table type is preserved by the assignment. So if the table was a DetermTable, it remains a DetermTable. If the variable was not a table of any type prior to the assignment, then the definition will be set to a Table.
If you want use assignment to change an existing DetermTable to a Table, you can first set X:=null
, followed by the array assignment, X:=A
. If you want to change an existing table to a DetermTable, it is best to use the MakeChangesInTable typescript command.
(new to Analytica 4.5)
If you assign a Boolean (0 or 1) to a variable defined as Checkbox(0) control, it stays as a checkbox, with the new value selected. For example:
Variable X := Checkbox(0)
An assignment in a user-defined function called from a Script:
X := 1
changes the definition of X to
Checkbox(1)
Similarly, if you assign a valid value v to a variable defined as a Choice(i, v) control, it retains the Choice() control:
Index Pet := ["Cat","Dog","Rat"] Variable Select_Pet := Choice(Pet,1)
After the assignment
X := "Rat"
the definition of Select_Pet becomes Choice(Animal,3)
, and the Choice menu remains.
This process only works if you assign a valid value, 1 or 0 (True or False) to a Checkbox, or a value from the index to a Choice control -- e.g. "Rat"
from Pet
. If you assign an invalid value that becomes the the new value of X
and it loses its Checkbox or Choice control, e.g.:
X := "Moose"
The new value of X is "Moose"
with no Choice() control.
(In previous releases, the definition was always reset simply to the value assigned in all cases and it lost the control Checkbox or Choice.) This also works when assigning a new value to the cell of a Table that contains a Checkbox() or Choice():
The assignment
(Pet_detail := Array(Pet_field,["Rat",1]))
preserves the controls, resulting in the new edit table
Assignment to Local Variables with Handles
There are several different constructs for declaring local variables, resulting in several nuances in behavior with regard to assignment. For most users of Analytica, these nuances are unimportant -- their primary relevance is for meta-inference, where the distinctions with respect to how handles are processed becomes important. With respect to assignment, the key distinctions are what happens when you assign a value to a local variable that is current holding a handle, and whether the local variable is declared as an index or not.
In general, local variables are declared either with a declaration construct (e.g., Var..Do), or through a parameter declaration in a User-Defined Function.
When assignment is made to a local variable that currently contains a handle to another object, there are three distinct things that may happen, depending on which type of local variable is being used:
- Meta-variable treatment
- The local variable is changed, no longer pointing to the object, leaving the object unchanged, and causing the local variable to now contain the new value.
- Alias treatment
- The assignment applies to the object, changing its definition. This behavior may require the evaluation to be launched from a button script or require the object to be defined via the ComputedBy function, since it involves a global side effect.
- Index treatment
- The assignment creates a new local index object with the new value.
This table shows how local variables are treated by assignment based on how they are declared:
How declared | Treatment type by Assignment (x := ...) | Notes |
---|---|---|
Local Declaration Constructs | ||
VAR.. DO | meta-variable | |
Using..Do | meta-variable | deprecated, identical to Var..Do |
MetaVar..Do | meta-variable | requires 4.2 |
LocalAlias..Do | Alias | requires 4.2 |
Index..Do | Index | |
MetaIndex..Do | Index | |
Function Parameter Declarations | ||
x : Context (or none) | Alias | |
x :Sample x:Prob x:Mid etc. |
Alias | |
x : Variable | Alias | |
x : Object | Alias | |
x : Index | Index | |
x : Handle | Meta-variable |
When you are performing meta-inference, it is recommended that you limit yourself to the MetaVar..Do and LocalAlias..Do constructs for declaring local variables. The Var..Do construct has been around in Analytica for a long time, long before it was widely used for meta-inference and manipulation of handles. As a result of its legacy, and the need to continue supporting backward compatibility, its treatment of handles is a less consistent, which can be a source of confusion. The LocalAlias..Do and MetaVar..Do variations were introduced for the purpose of providing two highly self-consistent, making their behavior easier to fully understand. Again, the distinctions only have a bearing when you are manipulating handles to other objects.
Examples
Var x := Handle(Va1);
x := 6 |
Meta-variable treatment
local x becomes 6. Va1 unchanged |
MetaVar x:=Handle(Va1);
x := 6 |
Meta-variable treatment
local x becomes 6. Va1 unchanged |
MetaVar x:=Handle(Va1);
x := Handle(Va2); |
Meta-variable treatment
local x is now holding a handle to Va2. Va1 unchanged |
LocalAlias x:=Handle(Va1);
x := 6 |
Alias treatment
local x unchanged -- still points to Va1. Va1's definition changed to 6 |
Index I := 1..5;
Var A := I^2; I := 10..15 |
Index-treatment
|
Suppose you have a meta-variable that points to Va1:
and now you want to use assignment to change the definition of Va1. Assigning to x doesn't do it, since this simply changes the value of x. To make the assignment, you need to force the alias treatment, which is done via LocalAlias as follows
LocalAlias x1 := x Do x1:=6
Consider the converse, where you have a LocalAlias to Va1:
LocalAlias x:=Handle(Va1);
and you want to change x to now be an alias for Va2 instead. This cannot be done, because x is in all ways an alias for Va1, so any attempt to change it will be enacting a change to Va1. What this should tell you is that if you have logic that is going to have to change what a local points to, you need to use a meta-variable (MetaVar..Do not LocalAlias..Do, or for function parameter qualifiers, Handle not Variable or Object).
Suppose you have a list of handles, and you want to set every variable in the list to 0. This can be done with a single LocalAlias statement,
LocalAlias x := ListOfHandles(Va1,Va2,Va3) Do x := 0
This works because of array abstraction. LocalAlias is psuedo-atomic in that it points to only one item (I say "pseudo-atomic" here because the object it is an alias for may have an array, so it doesn't limit the dimensionality of the value). So since x is being set to three handles, the body of LocalAlias is repeated three times, each time x is an alias of a different object.
Attribute Assignment with Handles
There is no functional difference between the following two examples
MetaVar x := Handle(Te1); Description Of x := "New text";
LocalAlias x := Handle(Te1); Description Of x := "New text";
In both cases, the description of Te1 is set. The second case should seem straightforward, but the first case with MetaVar appears to be setting the description attribute of x rather than the description attribute of Te1. However, it doesn't work this way because local variables are not objects -- they have no attributes themselves. Since the intent is unambiguous, the Of operator resolves a meta-variable (as well as a local alias) to the object pointed to. Thus, if you need to read or set an attribute value of the object held in your meta-variable, you don't have to obtain a local alias first.
Enable comment auto-refresher