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 |
---|
(Deprecated). We recommend you use Local..Do instead.
Var 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.
Var..Do is often used in a procedural syntax, where the declaration is followed by a semi-colon and the Do keyword is omitted, such as:
Var x := Sum(A, I);
Var y := Sum(B, I);
(x + y) * (x - y)
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.
1 + (Var a := b^2; Var c := a/b; c^2 - a - c - 2) + 5
Dimensionality Declaration
The allowed dimensions of a local value can be declared using the syntax:
Var «x»[«indexList»] := «expr» Do «body»
an equivalent anachronism (considered deprecated) is
Var «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.
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:
Var earnings[Time] := revenue-expenses;
Index 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:
Var 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:
Var «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:
Var n[] := n do Sum(Ln(1..n))
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 Var «x»[] := «expr» Do «body»
as long as the result of «expr» does not contain any Handles. It is actually equivalent to the following variation of Var..Do:
MetaVar «x»[] := «expr» Do «body»
Explicit Iteration
The following syntax:
Var «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 Var «x»[«I»] := ... syntax specifies the dimensions that are allowed. However, in this syntax, only a single index can be specified.
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:
Var n := 27;
Var steps := 0;
While (n > 2) Do (
steps := steps + 1;
n := if Mod(n, 2) Then n/2 else 3*n + 1
);
steps
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" 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:
Var 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.:
Var 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, two variations of Var..Do are preferred when Handles are involved, since these two variations have a cleaner semantics. They are:
- LocalAlias «x» := «expr» Do «body»
- or equivalently, Alias «x» := «expr» Do «body»
- MetaVar «x»[«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 MetaVar..Do, then the local identifier refers to an atomic value that has a data type of "handle". Operations such as «x»+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. As the declaration name MetaVar implies, this is usually the preferred method for declaring local values that are used to manipulate handles to objects. Your local value may contain a handle, or an array of handles, as well as other data types. Like Var..Do, you can declare dimensions inside a MetaVar..Do declaration (for example, the dimensions allowed to exist in the array of handles).
When a handle is assigned to a local «x» declared using Var..Do, it acts as a hybrid between a LocalAlias and a MetaVar, 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 then 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
Enable comment auto-refresher