Difference between revisions of "Assignment Operator :="

 
(35 intermediate revisions by 5 users not shown)
Line 1: Line 1:
 
[[category:Operators]]
 
[[category:Operators]]
[[Category:Doc Status C]] <!-- For Lumina use, do not change -->
+
 
 +
{{ReleaseBar}}
 +
 
 +
 
 +
__TOC__
 +
 
  
 
An assignment with syntax
 
An assignment with syntax
''v'' := ''expr''
+
:<code>''v'' := ''expr''</code>
sets the value of variable ''v'' to the value obtained from expression ''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 effects|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 improves transparency by banning assignments ==
+
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.
  
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.
+
== When Analytica allows assignment ==
  
== Analytica allows assignment in special cases==
+
Analytica does allow assignment in special cases that do not damage transparency unduly. You may assign to:
  
Analytica does allow assignments in these special cases that do not unduly damage this transparency:
+
* A local variable «v» that is declared in the same definition using [[Var..Do|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.
  
* You may assign to a local variable ''v'' declared in the same definition using [[Var..Do|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.
+
See below for details on each of these cases.
* 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 ==
 
== Assignment to a Local Variable ==
Line 25: Line 34:
 
This example shows the use of a local variable in an expression:
 
This example shows the use of a local variable in an expression:
  
VAR count := 0;
+
:<code>{{Local}} count := 0;</code>
FOR j:=I DO (
+
:<code>[[For]] j := I Do(</code>
  count := count + (A[I=j] <> Null)
+
:::<code>count := count + (A[I = j] <> Null)</code>
);
+
:<code>);</code>
count
+
:<code>count</code>
  
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.
+
This expression counts the number of elements of <code>A</code> over <code>I</code> that are not equal to <code>[[Null]]</code>. It assigns to count. The <code>[[For]]</code> loop also assigns consecutive values of <code>I</code> to <code>j</code>.  Note that if <code>A</code> has dimensions other than <code>I</code>, this expression works fine, and count will contain these other indexes.
  
== Assignment in OnClick or OnChange ==
+
== 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.
 
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:
+
Here is how to assign to a Global variable <code>X</code> in an [[OnClick]] or [[OnChange]] Attribute (or in a Function called from such an attribute):
Va1 := expr
+
:<code>X := expr</code>
 +
 
 +
The value of <code>X</code> becomes the result of calculating <code>expr</code>. To be more precise, the definition of <code>X</code> becomes the value of <code>expr</code>.
  
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:
+
== Assignment to an Attribute ==
( Va1 := expr )
+
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
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.
+
:<code>Units OF X := "KWh"</code>
  
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.
+
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:
 +
:<code>''attrib'' OF ''obj'' := ''expr''</code>
  
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.
+
You can remove an attribute by assigning [[Null]] to it:
 +
:<code>attrib of X := Null</code>
  
== Assignment to an Attribute ==
+
=== Assigning a Definition ===  
  
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
+
Assigning an expression to a Global variable sets its [[Definition]] to the value of the expression. For example
  attrib of obj := expr
+
:<code>X := 10^2</code>
  
An example is
+
evaluates 10^2 to obtain 100, and sets the <code>Definition</code> of <code>X</code> to 100.
  description of X := TextSentenceCase( TextReplace( Template, "%1%", "X") )
 
  
The right-hand side is evaluated, and the attribute is set to the value computed. 
+
You can change the Definition to an expression, without evaluating it, by assigning the expression as a text value to the [[Definition]]:
 +
:<code>Definition OF X := "Y^2"</code>
  
When used directly from a script, again parentheses are required, so that it is interpreted as an expression.
+
In this case, the value of <code>X</code> will change if <code>Y</code> changes.
  
The only way to remove an attribute value from a script is using:
+
So, these expressions are ''not'' equivalent:
attrib of X := Undefined
+
:<code>X := B</code>    Sets the Definition of X to the value of B
The related expression
+
:<code>X := "B"</code>  Sets the Definition of X to the text "B"
attrib of X := Null
+
:<code>Definition OF X := "B"</code>  Sets the Definition of X to the expression B.
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 ===
+
In the last case, if <code>B</code> isn't a defined Variable, it gives a syntax error.
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:
+
This table highlights the subtle differences between assigning an evaluated value to a Variable (as its new definition) and assigning a [[Definition]] directly:
  
{| class="wikitable"
+
:{| class="wikitable"
 
|-
 
|-
! scope="col"| Expression
+
! scope="col"| Assignment
! scope="col"| Resulting Definition of X
+
! scope="col"| Definition of X
 +
! scope="col"| Value of X
 
|-
 
|-
| X := 1+2
+
| X := 1 + 2
 +
| 3
 
| 3
 
| 3
 
|-
 
|-
| X := "1+2"
+
| X := "1 + 2"
| "1+2"
+
| "1 + 2"
 +
| "1 + 2"
 
|-
 
|-
| Definition of X := 1+2
+
| Definition OF X := 1 + 2
 +
| 3
 
| 3
 
| 3
 
|-
 
|-
| Definition of X := "1+2"
+
| Definition OF X := "1 + 2"
 
| 1+2
 
| 1+2
 +
| 3
 
|}
 
|}
  
<tip title="Note">The ''attrib of obj'' syntax does not set up edit-table definitions.</tip>
+
=== Assigning to a Value or ProbValue ===
+
 
== Assignment to [[RandomSeed]] ==
+
Although the Value (and [[ProbValue]]) attribute is not usually user-modifiable, you can assign a number, text, or [[Null]] directly to it, e.g.:
 +
:<code>Value OF X := 100</code>
 +
 
 +
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.
  
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.   
+
{{Release|6.1||
 +
=== 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 [[Attrib of Obj#Sub-Attributes|sub-attribute syntax]]. For example, to turn on the bevel for node <code>Va1</code>, you can use
 +
* <code>NodeInfo::bevel of Va1 :{{=}} 1</code>
 +
Since «bevel» is the ninth field in [[NodeInfo]], you can alternatively use
 +
* <code>NodeInfo::9 of Va1 :{{=}} 1</code>
 +
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,
 
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)
+
:<code>RandomSeed := 999;  Normal(0, 1)</code>
  
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.
+
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 =
+
=== Assigning in a Script Attribute ===
== Interactions of Assignment with Array Abstraction ==
+
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:
 +
:<code>(Va1 := expr)</code>
  
Analytica performs array-based operations.  When an operation has side-effects, as the assignment operator does, this may have some counter-intuitive resultsFor 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
+
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 <code>Va1</code> to the literal character-for-character expression written on the right-hand sideThe 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.
  
(A)
+
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.
var result := Uniform(0,1);
 
if result<0.5 then result := 0.5;
 
result
 
  
(B)
+
== Gotchas with assignment ==
var result := Uniform(0,1);
+
=== Interactions of Assignment with Array Abstraction ===
if result<0.5 then result:=0.5 else result:=result
 
  
  (C)
+
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:
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). 
+
:<code>(A)</code>
 +
:<code>{{Local}} result := [[Uniform]](0, 1);</code>
 +
:<code>[[If]] result < 0.5 [[Then]] result := 0.5;</code>
 +
:<code>result</code>
  
In case (A), when result<0.5 is evaluated (assuming [[Evaluation Modes|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.
+
:<code>(B)</code>
 +
:<code>{{Local}} result := Uniform(0, 1);</code>
 +
:<code>[[If]] result < 0.5 [[Then]] result  := 0.5 [[Else]] result := result</code>
  
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.
+
:<code>(C)</code>
 +
:<code>{{Local}} result := Uniform(0,1);</code>
 +
:<code>result := [[If]] result < 0.5 [[Then]] 0.5 [[Else]] result</code>
  
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.
+
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). 
  
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.
+
In case (A), when <code>result < 0.5</code> is evaluated (assuming [[Evaluation Modes|Sample mode]]), at least one element of result is likely to satisfy <code>result < 0.5</code>, so the THEN clause will be evaluatedWhen 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.
  
== Don't change a global index used by a transient array ==
+
In case (B), <code>result < 0.5</code> 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 <code>result := result</code> 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 <code>J</code> is a global index, and this is in a function called by a Script:
 
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 <code>J</code> is a global index, and this is in a function called by a Script:
  
[[Var]] x := Array(J, [1, 2, 3]);
+
:<code>{{Local}} x := [[Array]](J, [1, 2, 3]);</code>
J := Concat(J, ['D']);
+
:<code>J := [[Concat]](J, ['D']);</code>
F(x)
+
:<code>F(x)</code>
  
 
The assignment changing <code>J</code> makes <code>x</code> inconsistent. Global tables that use <code>J</code> use [[Table Splicing|spliced]] to keep them consistent, but there is no direct link from <code>J</code> to the transient array in local variable <code>x</code>, so it cannot be made consistent.  
 
The assignment changing <code>J</code> makes <code>x</code> inconsistent. Global tables that use <code>J</code> use [[Table Splicing|spliced]] to keep them consistent, but there is no direct link from <code>J</code> to the transient array in local variable <code>x</code>, so it cannot be made consistent.  
Line 147: Line 184:
 
Here is an example of a transient value that doesn't involve a local variable or parameter:
 
Here is an example of a transient value that doesn't involve a local variable or parameter:
  
Index I := 1..10
+
:<code>{{LocalIndex}} I := 1..10</code>
Function F()
+
:<code>Function F()</code>
Definition: (I+1)/(I := 10..100)
+
:<code>Definition: (I + 1)/(I := 10..100)</code>
  
<code>(I+1)</code> is a transient array indexed by <code>I</code>, which is held in memory as the denominator is computed. The computation of the denominator changes <code>I</code> -- including its length. The result is inconsisten.
+
<code>(I + 1)</code> is a transient array indexed by <code>I</code>, which is held in memory as the denominator is computed. The computation of the denominator changes <code>I</code> -- including its length. The result is inconsistent.
  
= Slice/Subscript Assignment =
+
== 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:
  
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:
+
:<code>v[I = x] := y</code>
 +
:<code>v[@I = n] := x</code>
  
v[I=x]  := y
+
This is only permitted when «v» is a local variable.
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:
 +
:<code>{{Local}} v := Va1;</code>
 +
:<code>v[I = x] := y;</code>
 +
:<code>Va1 := v</code>
  
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:
+
You could also accomplish this equivalently using:
 +
:<code>Va1 := [[If]] I = x [[Then]] y [[Else]] Va1</code>
  
var v := 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.
v[I=x] := y;
 
Va1 := v
 
  
You could also accomplish this equivalently using:
+
Additional information on [[Slice Assignment]] is available at [[Subscript/Slice Operator]].
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.
+
=== Assigning an Array ===
  
Additional information on Slice Assignment is available at [[Subscript/Slice Operator]].
+
If you assign an array value to a global variable (from an [[OnClick]] or [[OnChange]] attribute), e.g.
 +
:<code>X := [[Array]](I, [10, 20, 30])</code>
  
= Assignment to Tables =
+
it sets the Definition of X to a Table, with specified index and values, so it looks like this:
 +
:<code>Definition OF X &rarr;  Table(I)(10, 20, 30)</code>
  
''(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 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 use assignment to change an existing [[DetermTable]] to a [[Table]], you can first set <code>X:=null</code>, followed by the array assignment, <code>X:=A</code>.  If you want to change an existing table to a [[DetermTable]], it is best to use the [[MakeChangesInTable]] typescript command.
+
If you want to change the form of a [[Table]], for example from [[DetermTable]] to [[Table]], you should first set it to [[Null]]:
 +
:<code>X := NULL</code>
 +
:<code>[[Definition]] OF X &rarr;  [[Table]](I)(10, 20, 30)</code>
  
= Assignment to variables containing a Choice() menu or Checkbox =
+
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.
  
''(new to [[Analytica 4.5]])''
+
== 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:
 
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)
+
:<code>Variable X := Checkbox(0)</code>
An assignment in a user-defined function called from a Script:
+
 
X := 1
+
Assignment from the Onclick of a Button
changes the definition of X to
+
:<code>X := 1</code>
Checkbox(1)
+
 
 +
changes the definition of <code>X</code> to
 +
:<code>[[Checkbox]](1)</code>
 +
 
 +
Similarly, you can assign a valid value «v» to a variable defined as a [[Choice]](i, v) control:
 +
:<code>Index Pet := ["Cat", "Dog", "Rat"]</code>
 +
:<code> Variable Select_Pet := Choice(Pet, 1)</code>
  
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
 
After the assignment
  X := "Rat"
+
:<code>X := "Rat"</code>
the definition of ''Select_Pet'' becomes <code>[[Choice]](Animal,3)</code>, and the Choice menu remains.  
+
the variable retains its definition as a  [[Choice]] menu, but with the new value:
 +
<code>Select_Petm : [[Choice]](Animal, 3)</code>.
 +
 
 +
This also works for a variable defined with [[MultiChoice]](),
 +
:<code>Variable X := Multichoice(Animal, 1)</code>
 +
:<code>X := 'Dog'</code>
 +
after which
 +
:<code>Variable X :=Multichoice(Animal, 2)</code>
 +
 
 +
You can assign multiple values to a MultiChoice variable as a vector of values:
 +
:<code>X := ['Cat', 'Dog']</code>
 +
 
 +
You can also assign 'All' to a [[Choice]] or [[MultiChoice]] variable to select all its possible values.
 +
:<code>X := 'All'</code>
 +
 
 +
You can set a Multichoice variable to None (assuming you have set None as an option) by assigning an empty vector:
 +
:<code>X := []</code>
 +
 
 +
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. <code>"Rat"</code> from <code>Pet</code>. If you assign an invalid value that becomes the the new value of <code>X</code> and it loses its [[Checkbox]] or [[Choice]] control, e.g.:
 +
:<code>X := "Moose"</code>
  
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. <code>"Rat"</code> from <code>Pet</code>. If you assign an invalid value that becomes the the new value of <code>X</code> and it loses its Checkbox or Choice control, e.g.:
+
The new value of <code>X</code> is <code>"Moose"</code> with no [[Choice]]() control.
  X := "Moose"
 
The new value of X is <code>"Moose"</code> 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(), Choice(), or MultiChoice():
This also works when assigning a new value to the cell of a Table that contains a Checkbox() or Choice():
 
  
:Variable Pet_detail := <code>[[Table]](Pet_field)( [[Choice]](Animal,2), [[Checkbox]](0) )</code>
+
:<code>Variable Pet_detail := Table(Pet_field)(Choice(Animal, 2), Checkbox(0)) </code>
  
 
The assignment
 
The assignment
:<code>(Pet_detail := [[Array]](Pet_field,["Rat",1]))</code>
+
:<code>Pet_detail := Array(Pet_field, ["Rat", 1])</code>
 +
 
 +
preserves the Choice or Checkbox controls, resulting in the new edit table
 +
:<code>Variable Pet_detail := Table(Pet_field)(Choice(Animal, 3), Checkbox(1) )</code>
  
preserves the controls, resulting in the new edit table
+
Assigning to a [[Slider]] control preserves the control as well.
:Variable Pet_detail := <code>[[Table]](Pet_field)( [[Choice]](Animal,3), [[Checkbox]](1) )</code>
 
  
= Assignment to Local Variables with [[Handle]]s =
+
== Assignment to Local Variables with [[Handle]]s ==
  
 
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 [[handle]]s 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.
 
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 [[handle]]s 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 [[Function Parameter Qualifiers|parameter declaration]] in a [[User-Defined Function]].   
+
In general, local variables are declared either with a declaration construct (e.g., {{Local}}..Do), or through a [[Function Parameter Qualifiers|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:
 
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:
Line 228: Line 291:
  
 
This table shows how local variables are treated by assignment based on how they are declared:
 
This table shows how local variables are treated by assignment based on how they are declared:
{| border=1
+
:{| class="wikitable"
 
! How declared !! Treatment type by Assignment (''x'' := ...) !! Notes
 
! How declared !! Treatment type by Assignment (''x'' := ...) !! Notes
 
|-
 
|-
 
! colspan=3 | Local Declaration Constructs
 
! colspan=3 | Local Declaration Constructs
 
|-
 
|-
! [[VAR.. DO]]  
+
! [[Local..Do]]  
| meta-variable ||
+
| meta-variable || ''requires [[Analytica 5.0|5.0]]''
 +
(recommended)
 +
|-
 +
! [[Var..Do]]
 +
| meta-variable || {{Release|5.0||''deprecated''}}
 
|-
 
|-
 
! [[Using..Do]]  
 
! [[Using..Do]]  
| meta-variable || ''deprecated, identical to [[Var..Do]]
+
| meta-variable || ''deprecated.
 +
Identical to [[Var..Do]]
 
|-
 
|-
 
! [[MetaVar..Do]]  
 
! [[MetaVar..Do]]  
| meta-variable || ''requires 4.2''
+
| meta-variable || ''requires 4.2.
 +
Identical to [[Local..Do]]''
 +
{{Release|5.0||(deprecated)}}
 
|-
 
|-
 
! [[LocalAlias..Do]]  
 
! [[LocalAlias..Do]]  
| Alias || ''requires 4.2''
+
| Alias || (recommended)
 +
|-
 +
! [[Alias..Do]]
 +
| Alias || Same as [[LocalAlias..Do]]
 +
|-
 +
! [[LocalIndex..Do]]
 +
| Index
 +
| (recommended)
 
|-
 
|-
 
! [[Index..Do]]  
 
! [[Index..Do]]  
Line 251: Line 328:
 
! [[MetaIndex..Do]]  
 
! [[MetaIndex..Do]]  
 
| Index  
 
| Index  
| &nbsp;
+
| Identical to [[LocalIndex..Do]]
 
|-
 
|-
 
! colspan=3 | Function Parameter Declarations
 
! colspan=3 | Function Parameter Declarations
Line 278: Line 355:
 
|}
 
|}
  
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 [[handle]]s.  As a result of its legacy, and the need to continue supporting backward compatibility, its treatment of [[handle]]s 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 [[handle]]s to other objects.
+
When you are performing [[meta-inference]], it is recommended that you limit yourself to the {{MetaVar}} 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 [[handle]]s.  As a result of its legacy, and the need to continue supporting backward compatibility, its treatment of [[handle]]s 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 [[handle]]s to other objects.
  
== Examples ==
+
=== Examples ===
  
{| border=1
+
{| class="wikitable"
| [[Var]] x := [[Handle]](Va1);
+
| {{Local}} x := [[Handle]](Va1);
 
x := 6
 
x := 6
 
| '''''Meta-variable treatment'''''
 
| '''''Meta-variable treatment'''''
local ''x'' becomes 6.
+
local <code>x</code> becomes 6.
Va1 unchanged
+
<code>Va1</code> unchanged
 
|-
 
|-
| [[MetaVar]] x:=[[Handle]](Va1);
+
| [[MetaVar]] x:= [[Handle]](Va1);
 
x := 6
 
x := 6
 
| '''''Meta-variable treatment'''''
 
| '''''Meta-variable treatment'''''
local ''x'' becomes 6.
+
local <code>x</code> becomes 6.
Va1 unchanged
+
<code>Va1</code> unchanged
 
|-
 
|-
| [[MetaVar]] x:=[[Handle]](Va1);
+
| {{MetaVar}} x:= [[Handle]](Va1);
 
x := [[Handle]](Va2);
 
x := [[Handle]](Va2);
 
| '''''Meta-variable treatment'''''
 
| '''''Meta-variable treatment'''''
local ''x'' is now holding a handle to Va2.
+
local <code>x</code> is now holding a handle to <code>Va2</code>.
Va1 unchanged
+
<code>Va1</code> unchanged
 
|-
 
|-
| [[LocalAlias]] x:=[[Handle]](Va1);
+
| [[LocalAlias]] x := [[Handle]](Va1);
 
x := 6
 
x := 6
 
| '''''Alias treatment'''''
 
| '''''Alias treatment'''''
local ''x'' unchanged -- still points to Va1.
+
local <code>x</code> unchanged -- still points to <code>Va1</code>.
Va1's definition changed to 6
+
<code>Va1</code>'s definition changed to 6
 
|-
 
|-
| [[Index..Do|Index]] I := 1..5;
+
| {{LocalIndex}} I := 1..5;
[[Var]] A := I^2;
+
{{Local}} A := I^2;
  
 
I := 10..15
 
I := 10..15
 
| '''''Index-treatment'''''
 
| '''''Index-treatment'''''
:A new local index ''I'' is created with an [[IndexValue]] of [10,11,12,13,14,15]].
+
:A new local index <code>I</code> is created with an [[IndexValue]] of <code>[10, 11,12,13, 14, 15]</code>.
:The local I now refers to the new index.
+
:The local <code>I</code> now refers to the new index.
:Array ''A'' is indexed by the original index, ''A.I''.
+
:Array <code>A</code> is indexed by the original index, <code>A.I</code>.
 
|}
 
|}
  
Suppose you have a meta-variable that points to Va1:
+
Suppose you have a meta-variable that points to <code>Va1</code>:
:<code>[[MetaVar]] x := [[Handle]](Va1);</code>
+
:<code>{{MetaVar}} x := Handle(Va1);</code>
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:
+
and now you want to use assignment to change the definition of <code>Va1</code>. Assigning to <code>x</code> doesn't do it, since this simply changes the value of <code>x</code>. To make the assignment, you need to force the alias treatment, which is done via [[LocalAlias]] as follows
:<code>[[LocalAlias]] x:=[[Handle]](Va1);</code>
+
:<code>[[LocalAlias]] x1 := x Do x1:=6</code>
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 [[Function_Parameter_Qualifiers#Variable|Variable]] or [[Function_Parameter_Qualifiers#Object|Object]]).
+
 
 +
Consider the converse, where you have a [[LocalAlias]] to <code>Va1</code>:
 +
:<code>[[LocalAlias]] x:= [[Handle]](Va1);</code>
 +
 
 +
and you want to change <code>x</code> to now be an alias for <code>Va2</code> instead. This cannot be done, because <code>x</code> is in all ways an alias for <code>Va1</code>, so any attempt to change it will be enacting a change to <code>Va1</code>. 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}} not [[LocalAlias]], or for function parameter qualifiers, [[Handle]] not [[Function_Parameter_Qualifiers#Variable|Variable]] or [[Function_Parameter_Qualifiers#Object|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,
 
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
+
:<code>[[LocalAlias]] x := [[ListOfHandles]](Va1, Va2, Va3) Do x := 0</code>
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 ==
+
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 <code>x</code> is being set to three handles, the body of [[LocalAlias]] is repeated three times, each time <code>x</code> is an alias of a different object.
 +
 
 +
=== Attribute Assignment with Handles ===
  
 
There is no functional difference between the following two examples
 
There is no functional difference between the following two examples
  
[[MetaVar]] x := [[Handle]](Te1);
+
:<code>{{MetaVar}} x := [[Handle]](Te1);</code>
Description [[Of]] x := "New text";
+
:<code>Description Of x := "New text";</code>
 
 
[[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.
+
:<code>[[LocalAlias]] x := [[Handle]](Te1);</code>
 +
:<code>Description Of x := "New text";</code>
  
= See Also =
+
In both cases, the description of <code>Te1</code> is set. The second case should seem straightforward, but the first case with {{MetaVar}} appears to be setting the description attribute of <code>x</code> rather than the description attribute of <code>Te1</code>. 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 ==
 +
* [[Definition]]
 
* [[Subscript/Slice Operator]]
 
* [[Subscript/Slice Operator]]
* [[Var..Do]]
+
* {{Local}}..Do
 
* [[RandomSeed]]
 
* [[RandomSeed]]
 
* [[Scripting]]
 
* [[Scripting]]
 
* [[Attrib of Obj]]
 
* [[Attrib of Obj]]
 +
* [[Side effects]]

Latest revision as of 22:30, 29 April 2025




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  •  6.6




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:

Local count := 0;
For j := I Do(
count := count + (A[I = j] <> Null)
);
count

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 B
X := "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:

(A)
Local result := Uniform(0, 1);
If result < 0.5 Then result := 0.5;
result
(B)
Local result := Uniform(0, 1);
If result < 0.5 Then result := 0.5 Else result := result
(C)
Local 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 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:

Local 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:

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:

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.

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

Local 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 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

Comments


You are not allowed to post comments.