Assignment Operator :=
Release: |
4.6 • 5.0 • 5.1 • 5.2 • 5.3 • 5.4 • 6.0 • 6.1 • 6.2 • 6.3 • 6.4 • 6.5 |
---|
An assignment with syntax
v := expr
sets the value of variable «v» to the value obtained from expression «expr».
Avoid assignment to improve transparency
Analytica doesn't allow assignment to global variables, except in a few special cases. This comes as a shock to many experienced programmers because assignment is perhaps the most common operation in conventional computer languages. But there is a good reason to avoid assignment. In conventional languages, any function or module may have the "side effect" of assigning a new value to a global variable. A global variable may be assigned a new value almost anywhere in the code. So it's hard to be sure just where it got its current value -- a major reason that software is so hard to write, understand, and debug. Computer scientists term these side effects "non-locality" or "referential opacity". Analytica avoids this problem by requiring that each global variable gets its value from a single definition, which is part of the Variable object. So you know exactly where to look if you want to understand or debug the calculation for each variable. Experienced programmers often find this ban on global assignments takes a bit of getting used to. But, they soon discover the huge advantage of improved transparency.
When Analytica allows assignment
Analytica does allow assignment in special cases that do not damage transparency unduly. You may assign to:
- A local variable «v» that is declared in the same definition using Local, or LocalAlias, or where «v» is a parameter of the function. Since the local variable is defined and assigned in the same definition, you don't have to look far to understand its effect.
- A Global variable in an OnClick attribute of a Button or OnChange attribute of an input variable (or in the obsolete Script attribute). The user must actively click the Button or change the Input Variable. It actually changes the definition of the variable, so the result is clear.
- A Global variable in a user-defined Function that is called from an OnClick or OnChange attribute (or in a Function called from such a Function, and so on.)
- An Attribute of a Global variable in the same contexts that you may assign to the value of the variable.
- A Global Variable A in the Definition of a Global Variable B if A is defined as ComputedBy(B). This is useful if the definition of 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 in the Definition of A that it is computed in the definition of B.
- The RandomSeed system variable.
See below for details on each of these cases.
Assignment to a Local Variable
This example shows the use of a local variable in an expression:
This expression counts the number of elements of A
over I
that are not equal to Null
. It assigns to count. The For
loop also assigns consecutive values of I
to j
. Note that if A
has dimensions other than I
, this expression works fine, and count will contain these other indexes.
Assignment to a global variable 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.
Here is how to assign to a Global variable X
in an OnClick or OnChange Attribute (or in a Function called from such an attribute):
X := expr
The value of X
becomes the result of calculating expr
. To be more precise, the definition of X
becomes the value of expr
.
Assignment to an Attribute
You can assign directly to any user-modifiable attribute of a global object in an expression in an OnClick or OnChange Attribute (or a function called by such an action), for example
Units OF X := "KWh"
User-modifiable attributes include Class, Identifier, Title, Units, Description, and Definition. For a Module, you can also set Authors and Filename. All these Attributes expect a text value, so the «expr» should be, or evaluate to, an atomic text value. The general syntax is:
attrib OF obj := expr
You can remove an attribute by assigning Null to it:
attrib of X := Null
Assigning a Definition
Assigning an expression to a Global variable sets its Definition to the value of the expression. For example
X := 10^2
evaluates 10^2 to obtain 100, and sets the Definition
of X
to 100.
You can change the Definition to an expression, without evaluating it, by assigning the expression as a text value to the Definition:
Definition OF X := "Y^2"
In this case, the value of X
will change if Y
changes.
So, these expressions are not equivalent:
X := B
Sets the Definition of X to the value of BX := "B"
Sets the Definition of X to the text "B"Definition OF X := "B"
Sets the Definition of X to the expression B.
In the last case, if B
isn't a defined Variable, it gives a syntax error.
This table highlights the subtle differences between assigning an evaluated value to a Variable (as its new definition) and assigning a Definition directly:
Assignment Definition of X Value of X X := 1 + 2 3 3 X := "1 + 2" "1 + 2" "1 + 2" Definition OF X := 1 + 2 3 3 Definition OF X := "1 + 2" 1+2 3
Assigning to a Value or ProbValue
Although the Value (and ProbValue) attribute is not usually user-modifiable, you can assign a number, text, or Null directly to it, e.g.:
Value OF X := 100
This may set a Value for X that is inconsistent with its Definition. So we strongly discourage assigning to the Value attribute except in unusual circumstances.
Assignment to a sub-attribute
Several built-in attributes hold multiple fields (sub-attributes) separated by commas. You can assign to a single field using the sub-attribute syntax. For example, to turn on the bevel for node Va1
, you can use
NodeInfo::bevel of Va1 := 1
Since «bevel» is the ninth field in NodeInfo, you can alternatively use
NodeInfo::9 of Va1 := 1
The sub-attribute syntax works with these built-in attributes: NodeInfo, NodeLocation, NodeSize, DefaultSize, FontStyle, NodeFont, DiagState, WindState, ValueState, OutlinerState, DefnState, NumberFormat, ProbabilityNumberFmt, DensityNumberFormat, and FileInfo.
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.
Assigning in a Script Attribute
You may also assign in a Script attribute, but that is an obsolete feature, replaced by OnClick or OnChange attribute. One reason the Script attribute was replaced is that it uses a slightly different syntax, known as Typescript. For example, to assign to a variable in a Script, you shoud surround the the assignment operation in parentheses, such as:
(Va1 := expr)
Without parentheses, a script will interpret the expression as typescript rather than as an expression. It will not evaluate the right-hand side but rather set the definition of Va1
to the literal 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 recommend you use only the OnClick or OnChange attributes. We retain the Script attribute only for compatibility with legacy models.
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.
Gotchas with assignment
Interactions of Assignment with Array Abstraction
Analytica performs array-based operations. When an operation has side-effects, as the assignment operator does, this can have unexpected results. These three expressions may seem the same to someone accustomed to a procedural programming language, but only (C) produces the expected result. We assume they are each evaluated in Prob mode, so generate a random sample of values for 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 evaluated as an array operation, returning the correct truncating, and the assignment of the entire array occurs once.
To avoid this foible, don't use assignment 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:
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:
LocalIndex 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 inconsistent.
Slice/Subscript Assignment
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:
Local v := Va1;
v[I = x] := y;
Va1 := v
You could also accomplish this equivalently using:
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.
Assigning an Array
If you assign an array value to a global variable (from an OnClick or OnChange attribute), e.g.
X := Array(I, [10, 20, 30])
it sets the Definition of X to a Table, with specified index and values, so it looks like this:
Definition OF X → Table(I)(10, 20, 30)
If X was previously defined as a Table, DetermTable, ProbTable or IntraTable, it retains that form of Edit table, but using the Index(es) and values from the assigned Array.
If you want to change the form of a Table, for example from DetermTable to Table, you should first set it to Null:
X := NULL
Definition OF X → Table(I)(10, 20, 30)
If you want to change an existing Table to another form, such as DetermTable, ProbTable or IntraTable, it is best to use the MakeChangesInTable typescript command.
Assignment to variables with Checkbox. Choice, MultiChoice or Slider
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)
Assignment from the Onclick of a Button
X := 1
changes the definition of X
to
Checkbox(1)
Similarly, you can assign a valid value «v» to a variable defined as a Choice(i, v) control:
Index Pet := ["Cat", "Dog", "Rat"]
Variable Select_Pet := Choice(Pet, 1)
After the assignment
X := "Rat"
the variable retains its definition as a Choice menu, but with the new value:
Select_Petm : Choice(Animal, 3)
.
This also works for a variable defined with MultiChoice(),
Variable X := Multichoice(Animal, 1)
X := 'Dog'
after which
Variable X :=Multichoice(Animal, 2)
You can assign multiple values to a MultiChoice variable as a vector of values:
X := ['Cat', 'Dog']
You can also assign 'All' to a Choice or MultiChoice variable to select all its possible values.
X := 'All'
You can set a Multichoice variable to None (assuming you have set None as an option) by assigning an empty vector:
X := []
This kind of assignment 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.
This also works when assigning a new value to the cell of a Table that contains a Checkbox(), Choice(), or MultiChoice():
Variable Pet_detail :=
Table(Pet_field)(Choice(Animal, 2), Checkbox(0))
The assignment
Pet_detail := Array(Pet_field, ["Rat", 1])
preserves the Choice or Checkbox controls, resulting in the new edit table
Variable Pet_detail := Table(Pet_field)(Choice(Animal, 3), Checkbox(1) )
Assigning to a Slider control preserves the control as well.
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., Local..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
Local..Do
meta-variable
requires 5.0
(recommended)
Var..Do
meta-variable
deprecated
Using..Do
meta-variable
deprecated.
Identical to Var..Do
MetaVar..Do
meta-variable
requires 4.2.
Identical to Local..Do
(deprecated)
LocalAlias..Do
Alias
(recommended)
Alias..Do
Alias
Same as LocalAlias..Do
LocalIndex..Do
Index
(recommended)
Index..Do
Index
MetaIndex..Do
Index
Identical to LocalIndex..Do
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 Local and LocalAlias declarations 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, and MetaVar..Do is now Local..Do as of Analytica 5.0 and is the recommended method for declaring local identifiers for values. Again, the distinctions only have a bearing when you are manipulating handles to other objects.
Examples
Local 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
Local 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
LocalIndex I := 1..5;
Local A := I^2;
I := 10..15
Index-treatment
- A new local index
I
is created with an IndexValue of [10, 11,12,13, 14, 15]
.
- The local
I
now refers to the new index.
- Array
A
is indexed by the original index, A.I
.
Suppose you have a meta-variable that points to Va1
:
Local x := Handle(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 (Local not LocalAlias, 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
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 Local 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.
See Also
Enable comment auto-refresher