• • • 5.2
A handle is a reference or pointer to a variable, index, function, module, or other Analytica object. Using a handle lets you write variables or functions that work with the object itself rather than the value of the object.
About handles (varTerms)
Usually, if you refer to variable
X in an expression, say as:
Variable X := 100
Variable A := X
The value of
A will be equal to the value of
X, namely 100. But if you write:
Variable B := Handle(X)
B now refers to variable
X, and you can now access the
Title, Description, Inputs or
X. You can also create and use lists of handles to objects. For example, the list of indexes of an array, or the inputs or outputs of a variable.
If you define a variable as a list of variables:
Variable A := [X, Y, Z]
The value of
A will of course be an array, containing the results of evaluating the variables
X, Y, and
Z. The result will have
A an an Index (the [self Index]), which is a list of handles to
X, Y, and
Z. When displaying the array as a table, the Index A (usually) shows the titles of
X, Y, and
Z. The titles are blue, indicating that you can click each item to open the Object view for that object. If "Show By Identifier" is on (from the Object menu, or if you toggle it with control-Y), the index shows identifiers rather than titles. Either way, you can double click to open the object view.
Some attributes consist of lists of handles, notably
Inputs, Outputs, and
Contains (the objects in a module). The attribute
Isin is a handle to the Module containing this object. You can use these to write functions to find the ancestors, descendants, parents or contents of an object.
Returns a handle (pointer or reference) to object «v».
- Handle(v: Variable; AsIndex: optional boolean atomic)
If you want to create a user-defined function that returns a
Handle, in most cases the return value needs to involve a call to the Handle function. If you simply place the identifier of a local variable (even if it declared as
Variable) as the return value, it will be evaluated. For example, consider these two functions:
Function Fu1(X: Variable) := X
Function Fu2(X: Variable) := Handle(X)
Variable Va1 := 5
Suppose you evaluate
Fu1 returns the value of
Va1, that is 5.
Fu2 returns a handle to the
Va1 object. Hence, to return a
Handle, a call to Function Handle was necessary.
You can declare a local to be a Handle using, for example,
Local h := Handle(Va1) do ...
Va1 is the identifier of an object. In this case, «h» holds a handle. It is not an alias to
Va1, but it does serve to identify that object. You can have arrays of handles, and you can have many handles to the same object. When you later assign a different handle to
h, the object
Va1 does not change, but the local
h will then hold a different handle.
You can alternatively declare a local as assign it a handle using, for example
LocalAlias a := Handle(Va1) do...
In this case, within the scope of this LocalAlias declaration, «a» then becomes an alias for
Va1. In other words, anywhere you might use the identifier
Va1 in an expression, you could equivalently use
To create an alias to an index, use
The local identifier
I will then serve as an alias to the original index
In1 inside the scope of this var statement. The
AsIndex parameter causes
I's index values to be used when
I. appears in a value context (this is relevant when the original object is a self-indexed array, having both a value and an index value).
When an index has handles as elements, and you wish to subscript across that index, the recommended syntax is:
A[I = Handle(x)]
In this example,
x would be the identifier appearing as one of the elements of
- ListOfHandles(identifiers: Repeated Object)
Returns a list of handles to one or more objects specified by their identifiers. For example:
ListOfHandles('X', 'Y', 'Z')
returns a list of handles to the three variables. You get the same result from:
[Handle(X), Handle(Y), Handle(Z)]
but the first is usually more convenient. There is another distinction. If you assign the first example, using ListOfHandles, to an Index, it will uses actual handles, which show as clickable blue links containing the title of each object in a table. If you assign the second example, using a simple list, to an Index, each element in the index will appear as the expression, Handle(X), etc, which is not usually what you want.
- HandleFromIdentifier(varName : atomic text)
Returns a handle to a variable or other object with the identifier «varName». If no such object exists, it returns Null.
Caution -- no auto-updating from text attributes: If you pass the identifier of a variable
V as text to this function, as in
Variable X := HandleFromIdentifier("V")
the value of
X will be a handle to
V, but if the value of
V changes it will not automatically recompute
X and any of its dependents that may depend on the value of
V. Or if you change the identifier of
V, it will create an error when X is evaluated since the identifier
"V" is no longer in use. In this case, you can avoid these problems simply by defining
Variable X := Handle(V)
in which case Analytica's automatic dependency tracking will know to update
V changes. But, that might not be possible if the parameter is the result of a more complex expression that finds the identifier of
Handle to calling object
A user-defined function can obtain a handle to the variable, function, button or other object that called the function, using:
The tag "_Caller" will not conflict with an identifier since identifiers may not normally begin with "_". This is useful when you want to write a function that bases its behavior based on an Attribute of the variable that invokes it. For example, an error message function can use it to generate a message that identifies the variable, function, or button in which the error occurred without you having to pass the object as an explicit parameter the function.
- IndexesOf(A : Array)
Returns a list of handles, each one serving as a handle to an index of array «A».
It is possible for an array to have more than one local index having the same identifier. Obviously, we don't recommend, but in the unusual case where this occurs, the handles returned by IndexesOf are unambiguous, where the results of IndexNames are ambiguous.
Local Variables and Handles
The value of a local variable, like a global variable, may be a number, text, or handle or an array or scalar. When a local variable is set to a single handle (not an array), there are two behaviors that are possible, depending on the type of local variable.
A Local-declared local contains whatever you assign it. Whenever you use its identifier in a value context, its identifier acts as the value assigned to it. So if you use a Local identifier in a value context, its value is a handle to an object (not the value of the object pointed to). When you assign to a Local, you are just changing its value -- the object previously pointed to is not impacted.
A LocalAlias local acts as an alias of another object when it is set to be a handle. Any use of a LocalAlias's identifier in an expression is equivalent to using the identifier of the object pointed to (at least for objects that exist in the global namespace). If you assign a new value to a LocalAlias-type local, you change the object pointed to, and the local remains an alias to that same variable (note: you are only allowed to change the value of another object when the assignment is evaluated from a button script).
When writing meta-inference algorithms, where your algorithm will be manipulating handles to objects, we recommend that you declare all your local variables using only MetaVar..Do or LocalAlias..Do depending on your intention, and avoid the use of Var..Do, or even For..Do. The recommendation is only for your own conceptual simplicity, since MetaVar and LocalAlias have such clean semantics, it is less confusing than using Var or For.
When a local variable that is declared using Var..Do is assigned a handle, it acts as a hybrid of the MetaVar and LocalAlias semantics. In a value context, its identifier acts as an alias. However, when you assign to it, the local is changed to the new value without changing the object originally pointed to.
You may find yourself iterating over a list or array of handles, where you intend to operate on each handle in the list. Rather than use:
For v := ListOfHandles Do body
it is better to use one of:
In each iteration of «body», «y» will be an alias of the object pointed to in the first form, and «z» will contain a handle to the object in the second form.
The function IsHandle(X) tests whether the value contained in «X» is a handle. If the value of «X» is an array, then it tests whether each cell of the array contains a handle, and returns an array of 0s and 1s.
The call IsHandle(X, local: true), where «X» is a local variable, tests whether the local variable contains a handle. Note that if you don't include the
local: true parameter, you are testing the value contained within the local, rather than the local itself (except in the case of a meta-local, in which case the test is valid even without the optional
local: true). See IsHandle for details.
Display of handles in Result Tables
In an index or array, a cell containing a handle shows the title of the object in blue. (or its identifier if "Show by identifier" is selected. Press Ctrl-Y to toggle this preference.)
Double click on such a cell to displaying a handle to show the object. By default, it shows the object's node highlighted in its parent Diagram. You can change this default behavior for displayed handles in the value of a variable by setting its Attribute Att_hyperlinkPref. If you set Att_hyperlinkPreffor a module, it controls behavior for all variable in that module (and its submodules). The attribute has three possible values:
1= Open its object window
2= Open its parent diagram, with object highlighted
3= Same as
2, except that for a Module, it opens its diagram with nothing selected.