Namespaces
New to Analytica 6.5, and still experimental, not fully exposed, unsupported and subject to change.
This page describes a feature that is only partially implemented (as of Analytica 6.5 beta). Despite being incomplete, it is in a state where it is useful for some purposes, including Lumina-internal development, and hence is documented here. It can be extremely useful, but may be subject to change between now and feature completion.
What is a Namespace?
A global identifier is a name for a global object. Each global object belongs to a namespace and within that namespace, its identifier must be unique. For example, you cannot have two objects both named X
in the same namespace. In fact, if you try to create a second object named X
in the same namespace, Analytica will change it to be unique by adding numbers to the end, for example by changing the new name to X1
. However, you can create a second object named X
in a distinct namespace,
An object with a module or library class or subclass can be configured to be a namespace. An object of one of these classes is not namespaces normally a separate namespace, so it must be explicitly configured to be so. Filed modules and libraries are automatically configured to be namespaces, and an object with class Model is automatically a namespace.
Internally, the presence of the NamespaceDefaultScope attribute on a module subclass with a value of either Public
or Private
is what actually determines whether a module object is a namespace. Modules or libraries without this attribute are not namespaces, whereas those with this attribute are. The attribute value must be either Public
, Private
or Not A Namespace
, with the meaning of this value is described below.
In addition to your model's namespace(s), there is also a system (or root) namespace which holds the built-in functions, system variables, etc. Analytica will usually add numbers to the end of your identifier it collides with an identifier in the system namespace, but in theory that CAN co-exist. When a system object is added to Analytica in a new release, collisions with existing legacy models do arise, and in this case Analytica allows both to have the same name.
Use Cases
When you have multiple people working on the same model, it is common to partition the work into Modules. Two people may end up creating variables within their own module that have the same name, but which might not be actual duplicates of the same quantity. When you merge the work together, this results in identifier collisions which must be dealt with. One nice solution is to configure each person's module to be a namespace so that these duplicate names don't actually collide, eliminating the need to rename someone's variables.
When you create a library to be used by others, you have no way to know that eventual users will never duplicate an identifier inside your library. Hence, it makes a lot of sense for your libraries to be namespaces. If fact, this is so compelling that Analytica will do this automatically when you Add Library... or Add Module... or use a filed module.
Qualified Names
When there are multiple identifiers with the same name, each in a different namespace, you may need to disambiguate which object you want by using a qualified namein the form NS :: X
. For example, suppose NS1
and NS2
are names of two distinct namespaces, each containing a different object named X
. Then you could use NS1 :: X
or NS2 :: X
to disambiguate which object you want.
Namespace names are themselves identifiers and thus subject to the same properties of other identifiers -- i.e., two namespaces might have the same name when each is in a distinct namespace. Thus, you might use a qualified name to get to the namespace of interest, resulting in a namespace path such as: NS1 :: NS2 :: X
.
The operator ::
is called the namespace operator. It has the highest precedence and cannot be broken up by parentheses. It must contain an identifier, qualified identifier or nothing on its left, and an identifier on its right.
You can begin a qualified name at the root/system namespace by starting out with ::
such as ::Npv( cash_flow, Time, 5%)
. When you start at the root, it will match system objects first, and then objects that are in scope in your model namespace.
Each non-terminal component of a qualified name must be the identifier of a namespace that is in scope within the namespace identified by the path up to that point. These cannot be identifiers for modules that are not configured to be namespaces. However, there is one exception to this. When creating a new object using the typescript syntax
- «className» «newIdentifier»
the «newIdentifier» can specify the parent module as the penultimate component of the path. For example,
Decision Sales :: Incentive_discount
would create a decision variable named Incentive_discount
inside the module Sales
, and Sales
does not itself need to be a namespace.
In many cases you can use an unqualified identifier to access an object that resides in a different namespace. This is possible when the identifier is unambiguously imported or is inherited by the current namespace, which are described below.
Namespace Imports
A namespace, say NS1
, can import another namespace, NS2
, which makes all the public identifiers in NS2
visible from NS1
without having to use a qualified path.
You specify a namespace import by adding the imported namespace's identifier (or qualified identifier) to the receiving namespace's NamespaceImports
attribute. For example, in typescript:
NamespaceImports NS1 : NS2
If NS1
imports multiple namespaces, they should be listed one on each line, e.g.,
NamespaceImports NS1 : NS2~ NS3~ NS4::NS5
In the above example, the identifiers directly in NS4
are not imported but those in NS5
are.
A NamespaceImports attribute has no effect (is ignored) when it is set for a non-namespace object.
When a child (or grand child) module is configured to be a namespace, the parent module does not automatically import the identifiers is those submodules. You must add the child module to the parent's NamespaceImports
attribute in order to use the identifiers inside the submodule without a qualified path. Note that this is different from the case where the child module is not configured to be a namespace, since in that case, all the objects in the child actually belong to the parent's namespace. (Note: In the future, when namespace features are exposed on the Analytica GUI, it'll automatically add the import to the parent namespace when you first configure a module to be namespace, thus requiring you to remove the import if you don't want that. At present, since you have to configure namespaces by setting attribute values, you need to do both.).
What one namespace imports multiple other namespaces, identifier ambiguity can arise.
Namespace scope
Every global object has either public or private namespace scope. This scope determines whether the object's identifier imported when one namespace imports another namespace. The scope for a given object can be specified explicitly, or it is inherits the default scope for its own namespace.
To explicitly set the scope of a given global object (which can be of any object class), set the NamespaceScope
attribute to be either Public
or Private
. For example, in typescript,
NamespaceScope::Internals : Private
When an object does not have its NamespaceScope
attribute set, its scope is the value in its namespace's NamespaceDefaultScope
attribute.
When you import from a public namespace, i.e., one whose NamespaceDefaultScope
is Public
, the importing namespace gets all identifiers except those explicitly marked Private
.
Conversely, when you import from a private namespace, i.e., one whose NamespaceDefaultScope
is Public
, the importing namespace gets only those identifiers explicitly marked as Public
.
The NamespaceScope
attribute on the namespace object itself has a few additional ramifications. Consider a case where NS1
imports NS0
. The identifier for NS0
is always in scope in NS1
for either value of NamespaceScope of NS0
However, now suppose NS2
imports NS1
. In the case where NamespaceScope of NS0
is Public, the identifier NS0
is imported into NS2
(i.e., can be used from NS2
without qualifiers). In the case where NamespaceScope of NS0
is Private, the symbol NS0
is not imported into NS1
(as would be the case with any object, even a non-namespace), but in addition, the identifiers inside NS0
are not imported into NS1
. Hence, in this later case, the public identifiers in NS0
are available without qualifiers in NS1
but not in NS2
. This latter case matches a common scenario:
- Model MyModel (
NamespaceImports: MyLib
)- Variable SomeVar
- Library MyLib (
NamespaceDefaultScope:Public
,NamespaceImports: Internals
)- Function F(x)
- Module Internals (
NamespaceDefaultScope:Public
,NamespaceScope:Private
)- Function PrivateF(x)
The function PrivateF
is in scope from Definition of F
, meaning it can be used there without any qualifiers. It could also use Internals::PrivateF
, but the qualifiers would be unnecessary. However, PrivateF
is not in scope from the Definition of SomeVar
, but F
is in scope.
Private scope does not prevent other namespaces from reaching an object, it simply controls whether a names can be used without qualifiers. Hence, SomeVar
could call MyLib::Internals::PrivateF(..)
.
Namespace inheritance
A namespace automatically inherits all identifiers that are visible in its parent's namespace.
Since its parent's namespace automatically inherits from its parent's namespace, it means that a namespace also automatically inherits from its grand parent namespace, and so on, all the way up to the root / system namespace.
A collision of identifiers up the inheritance path is not an error. For example, if X
is in scope in the parent namespace, and a different X
is in scope in the grandparent's namespace, then X
would be ParentNS::X
.
Precedence
There are three ways for an identifier to be in-scope in a namespace:
- The object itself resides in the namespace.
- It is imported from another namespace. This includes imports from child namespaces, as well as from unrelated namespaces.
- It is inherited from a parent or ancestor namespace. This includes the root/system namespace at the top.
An identifier in the current namespace always takes precedence over an imported on inherited identifier with the same name. This is not considered an ambiguity or name collision.
An identifier that is imported always takes precedence over an inherited identifier with the same name. Again, this does not constitute an ambiguity (name collision).
Two identifiers with the same name that are both imported is ambiguous and constitutes a name collision. In such a situation, it would be an error to use an unqualified name. You must use a qualified name to disambiguate.
When two identifiers with the same name are both inherited, the more closely related one (with the fewest parent-namespace steps) always takes precedence. This is not considered an ambiguity.
It follows from these that system identifiers are at the lowest precedence.
When you have two identifiers with the same name that are in scope, and you want to use the one at the lower precedence, you can do so by using its qualified name.
You can set these attributes:
- NamespaceDefaultScope
- Specifies that a module object is also a namespace. Its value specifies the namespace scope of all objects in the namespace that don't themselves have an explicit
NamespaceScope
attribute set.- It is meaningful only on a Module or library object, or other subclass (module subclasses include Module Library LinkModule, LinkLibrary, Model, Form, SysLibrary, PluginLibrary). Its value has no effect (is ignored) on any other object type.
- Recognized values: Public, Private, Not a namespace.
- Determines whether a module is also a namespace. It is if it has a value of Public or Private.
- The value determines the namespace scope for all objects inside the namespace who don't have their own
NamespaceScope
attribute set.
- NamespaceScope
- Specifies whether the object's identifier is exported to a remote namespace that imports the object's own namespace.
- Recognized values: Public, Private
- When not set, the namespace scope is taken from its namespace's
NamespaceDefaultScope
attribute value. - Public scope means its identifier is exported to an importing namespace.
- Private means its identifier is not exported to the importing namespace.
- On a namespace object, the above applies, but also Private means that identifiers in its namespace are not "re-exported" when a remote namespace imports a namespace that imported it.
- NamespaceImports
- Specifies a list of other namespaces that are imported into this namespace.
- Only meaningful on a namespace module. Ignored otherwise.
- List one namespace name on each line. The identifiers used must be in context from the namespace's own scope, and if not, you can use a qualified name.
- There is no need to import parent or ancestor modules, since they are implicitly inherited.
These attributes are used internally, and are read-only:
- NamespaceParsedImports
- A parsed version of the NamespaceImports attribute.
- NamespaceExportsTo
- The inverse of the
NamespaceParsedImports
attribute.
Enable comment auto-refresher