Var..Do
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 |
---|
The Var
is now deprecated. We recommend you use Local..Do instead.
Local x := expr Do body
Declares a local value with identifier «x», so that the identifier «x» refers to the value obtained by evaluating «expr». The identifier «x» can then be referred to within the «body» expression. The expression «body» is said to be the lexical context of «x», since outside of the lexical context, the identifier «x» is not recognized.
Local is often used in a procedural syntax, where the declaration is followed by a semi-colon and the Do keyword is omitted, such as:
With this syntax, the lexical context for «x» extends from the expression immediately following the semi-colon to the end of the sub-expression that the Var..Do declaration is embedded in. For example, in the following expression the lexical scope of a
is shown in green.
You can declare multiple local identifiers on the same line by separating them with commas. All have the same lexical scope.
Local a:=J, b, c, d:=2;
When the :=«expr» is omitted, the local is initialized to Null. So in the preceding example, b
and c
are set to Null.
When a function has multiple return values, you can capture these into separate locals by placing the local names in parentheses. For example, the function SingularValueDecomp returns 3 matrix values.
Local (u, w, v) := SingularValueDecomp(a, I, J, J2);
Dimensionality Declaration
The allowed dimensions of a local value can be declared using the syntax:
Local «x»[«indexList»] := «expr» Do «body»
an equivalent anachronism (considered deprecated) is
Local «x» := «expr» in each «indexList» Do «body»
There are some situations where the extra information about which indexes are allowed is required in order to ensure that the «body» expression will array abstract correctly when new dimensions are added to a model later.
When the allowed indexes are declared, Analytica will ensure that when «body» is evaluated, the value of «x» will not have any indexes not listed in «indexList». If the original value assigned to «x» has indexes beyond those found in «indexList», Analytica will automatically iterate, evaluating «body» multiple times one slice at a time.
- Note: You should not make any assumptions about the order of the iteration, or an assumption that every index value will actually be visited. Our spec for Local allows the leeway for the expression compiler to re-order iteration order, to skip cases that it can prove will not impact the final result, or even to evaluate multiple cases concurrently. Speed optimizations like these have no detectable effect on your result except in the case where «body» has side-effects. Side-effects may include user-interactions (like MsgBox), writing/logging to files, or assignment. In contrast, the For..Do declaration also iterates while also guaranteeing sequential ordered and complete visitation of all cases. Because a Local declaration allows the expression compiler extra leeway to apply speed optimizations, Local..Do declarations are usually preferred to For..Do since they are declarative and faster, unless, of course, you rely upon procedural programming style side-effects within «body». You may find it conceptually convenient to imagine Local..Do iterating over each non-allowed index combination, which is logically equivalent to what actually happens as long as you have no assignments or other side-effects within the «body».
If the result of «expr» does not already have all the indexes declared in «indexList», the missing indexes are NOT added to «x».
Example
The following computes the standard deviation across only the time periods that are profitable:
Local earnings[Time] := revenue-expenses;
LocalIndex profitTimes := Subset(earnings > 0);
SDeviation(earnings[Time = profitTimes], profitTimes)
Without the dimensional declaration restricting earnings
to the Time index, Subset would complain that earnings has more than dimension in the event that revenue-expenses
has an index in addition to Time. The dimensional declaration here allows the expression to fully array abstract if new dimensions are added to the model.
The above expression is meant to be illustrative, but for completeness we also note an alternative expression for the same computation that does not require iteration:
Local earnings := revenue - expenses Do SDeviation(earnings, Time, w: earnings > 0)
Atomic Declarations
A special case of the dimensional declaration is the declaration that a local value must be atomic -- i.e., a single non-array value. In this case, the we simply specify a zero-length list of allowed indexes:
Local «x»[] := «expr» Do «body»
Then inside «body», «x» is guaranteed to be atomic.
Example
The following computes the log-factorial of a number in an array-abstractable fashion (i.e., works even if n is originally an array:
Note: The local value can have the same identifier as a global variable, and the value of the global can appear within «expr» since that is outside the local identifier's lexical scope. Inside «body», the identifier always refers to the local value. Having two local values with the same identifier is not allowed.
Atomic..Do syntax
Analytica also recognizes the following syntax for declaring a local value as atomic:
Atomic «x» := «expr» Do «body»
This syntax is equivalent to Local «x»[] := «expr» Do «body»
Explicit Iteration
The following syntax:
Local «x» := «expr» In «I» Do «body»
evaluates «expr», then iterates over each element of index «I», setting «x» to the «expr»[«I» = i] slice while «body» is evaluated. In a sense, this is a dual to the dimension declaration -- here we are specifying the dimensions that are not allowed in «x», while the Local «x»[«I»] := ... syntax specifies the dimensions that are allowed. However, in this syntax, only a single index can be specified.
This dual style iteration is very rarely used.
Assignment
Although side-effects are generally prohibited from within Analytica expressions (due to dependency-maintenance and Analytica's adherence to the principle of referential transparency), you can change the value of a local value using the assignment operator, :=. For example:
Assignment always resets the value of «x», even if «x» contains a handle. In other words, when you assign to a local value, you are resetting the value that the local identifier refers to, as opposed to changing the value of the object pointed to by the local value. See more in the section below on Meta-Inference.
Slice assignment
You can also assign to individual slices of a local value. This is described in detail at Slice assignment.
Evaluation Mode
A local value refers to a value, not an object. Hence, the terminology "local value" (or just "local") should be used and it should not be called a "local variable". A local value is not a variable -- a variable in an object that has attributes, has a separate mid-value and sample-value, and usually appears on an influence diagram. A local value has no attributes, does not appear in the global namespace, and does not maintain a separate Mid- and Sample-value.
When the local value is declared, «expr» is evaluated in the current Evaluation mode. From that point on, «x» becomes an alias for the value that resulted from that evaluation, whether or not the identifier «x» appears in Mid- or Sample- context. This can be a source of confusion. Consider the following example:
Local u := Uniform(0, 1);
SDeviation(u)
When this expression is evaluated in Mid mode it is not equivalent to SDeviation(Uniform(0, 1))
. The later evaluates to 0.29, while the former results in 0. This is because u
is assigned Mid(Uniform(0,1))
, which is 0.5, and then the result is SDeviation(0.5)
, which is zero.
You can, of course, call Sample() or Mid() explicitly from «expr» when desired, e.g.:
Local u := Sample(Uniform(0, 1));
SDeviation(u)
This confusion can be avoided by adhering to and conceptualizing the terminology that «u» is a local value, not a local variable.
Meta-Inference and the use of handles
Most models built in Analytica make no use of handles, and so the considerations described here impact only the most advanced modelers. Inference involving handles provides a mechanism for meta-Inference -- that is, reasoning about or altering your model from within Analytica itself. Advanced uses of meta-Inference can be used to extend Analytica's capabilities in many ways, creating functionality in your model beyond what is offered directly by the Analytica interface.
A handle is essentially a pointer to an Analytica object, such as a Variable, Index, or Module object. Meta-inference implementations usually need to store handles inside local values, assign handles to local values, read information about the objects pointed to by these handles, and manipulate the objects pointed to by these handles.
When implementing meta-inference algorithms, you should never use Var..Do to declare locals, but instead should use Local or LocalAlias..Do. The older Var..Do syntax suffers from several inconsistencies with how handles are treated, which is why is new deprecated (as of Analytica 5.0).
- LocalAlias «x» := «expr» Do «body»
- or equivalently, Alias «x» := «expr» Do «body»
- Local «h»[«indexList»] := «expr» Do «body»
When LocalAlias..Do is used to assign a handle to «x», then «x» is treated everywhere as an alias of the object pointed to. If you were to copy the expression and substitute the object's identifier everywhere «x» appears (assuming the object is in the global namespace), you would get the identical result. Once the local «x» is assigned a handle, you can no longer change the handle (i.e., change which object is pointed to), since an assignment, «x» := z, would be interpreted as an assignment to the object pointed to, rather than changing what «x» refers to. You cannot declare dimensions in a LocalAlias..Do or Alias..Do declaration.
When a handle is assigned to a local value declared as Local, then the local identifier refers to an atomic value that has a data type of "handle". Operations such as «h»+1 do not make sense, since this would be attempting to add 1 to a handle value, rather than adding 1 to value of the variable pointed to by the handle. Your local value may contain a handle, or an array of handles, as well as other data types.
When a handle is assigned to a local «x» declared using Var..Do, it acts as a hybrid between a LocalAlias and a Local, which is confusing, so that we recommend that you do not use Var..Do with values containing handles. In a value context, «x» acts as an alias to the object. However, in an assignment context (an L-value context), it a acts like a local value, in which the local «x» changes to refer to the new value, rather than causing the object pointed to by «x» to be changed. Consider:
Var x := Handle(A);
x := x + 1
Here x
is first assigned a handle to A
. In the assignment operation, when the right-hand side of the assignment is evaluated, x + 1
refers to the value of A
plus 1. Hence x
acts as an alias to A
. The assignment changes what the value the local identifier refers to, but does not alter A
. After the assignment, the local x
contains a numeric value (or perhaps array of numeric values) and no longer points to the variable A
.
Suppose in the above example that A
evaluates to a self-indexed array. The right-hand side of the assignment is a value context, so in this case, x
refers to the array-value of A
. If we wanted x
to alias the index value of A
, rather than the array value, we could use the following instead:
Var x := Handle(A, asIndex: true);
x := x + 1
When you assign a handle to a local identifier that has been declared using Local, the value itself has a data type of Handle. This can be contrasted with locals declared using LocalAlias, for which the identifier is the identifier of the object. Consider this example
- Local h := Handle(Va1);
- h := 2
- LocalAlias x := Handle(Va3);
- x = 3
After this code is evaluated, the object Va1
remains unchanged, the local h
now has a value of 2 instead of a handle, and the Definition of the global variable Va2
has been changed to 3. The local x
still refers to the same variable as the global identifier Va3
. The last line is only allowed in a context that allows side-effects to global variables, such as from a button script.
When you have a handle to an object in a Local, and you want to use it as if it were a global variable, you do this by using LocalAlias as illustrated here
- Local h := Handle( I ); { The local with the handle to an object, in this case an index }
- LocalAlias J := h;
- Sum( a, J )
History
There has been a long history of changes and enhancements to the Analytica language's syntax and treatment of local variables, which has lead to a proliferation of different constructs for declaring local variables, with subtle distinctions between them, and many of the distinctions really just required to support backward compatibility for models created in older releases of Analytica. Starting with Analytica 5.0, this complexity has been simplified, so that now there are only three ways you need to know for declaring locals:
- Local v := ...;
- LocalAlias x := ...;
- LocalIndex I := ...
In addition, you may want to use For..Do in some occasions, which also introduces a local identifier.
The Local keyword was introduced in Analytica 5.0 as is equivalent to MetaVar..Do. MetaVar..Do was introduced (in release [fill in]) to correct inconsistencies in Var..Do. Var..Do still has those inconsistencies, which can make it confusing to use when writing code that involves handles, but it continued to be used extensively since MetaVar..Do had such an unnatural name. In Analytica 1.0, local variables were declared via a Using..Do construct, now considered very archaic and seldom seen.
Analytica 5.2 introduced several enhancements to local declarations. These include the ability to omit the «expr» value for a local (so that it defaults to Null, and to declare multiple identifiers in the same declaration, both of which are illustrated by
Local a, b, c;
It also introduced reduced
dimensionality qualifier, and the ability to capture multiple return values.
Enable comment auto-refresher