Scoping Details#

Structures#

Halerium structures are built out of graphs, entities and variables. The building blocks can be nested to build deep and hierarchichal structures in a convenient way with the scoping mechanism.

Scoping#

Scope#

A scope is the context in a halerium structure. Scopes are managed via entering and exiting python with blocks.

Example:

a = Graph("a")
b = Graph("b")
# here the current scope is `noScope`
with a:
    # here the current scope is `a`
    pass
with b:
    # here the current scope is `b`
    pass

Scopetor#

A scopetor is an object that can provide such a scope. The halerium scopetor classes are Graph, Entity, Variable, and StaticVariable. So in the above example a and b could have been instances of any of these classes. Additionally, every Graph instance comes with an instance of Inputs and Outputs. These are scopetors as well

g = Graph("g")
with g:
    with g.inputs:
        # the current scope is `g.inputs`
        pass
    with g.outputs:
        # the current scope is `g.outputs`
        pass

Scopee and child#

A scopee is an object that lives in a scope.

>>> g = Graph("g")
>>> with s:
>>>     e = Entity("e") # `e` now lives in `g`. So the scope of `e` is `g`
>>> print(e.scope is g)
True

All scopetors can also scopees as well as all operators. If a scopee is also a scopetor it is referred to as a child of its scope.

>>> g = Graph("g")
>>> with s:
>>>     e = Entity("e") # `e` is now a child of `g`.
>>> print(e in g.children.values())
True

All children of a scopee can be accessed as properties of the scopetor by their name.

g = Graph("g")
with g:
    e = Entity("e")
    with e:
        v = Variable("v")

>>> g.e.v is v
True

Graphs start with two children, inputs and outputs

>>> g = Graph("g")
>>> print(g.inputs.name)
inputs
>>> print(g.outputs.name)
outputs

Allowed combinations#

Not all scopetor/scopee combinations are allowed. There is a hierarchy. Graphs can contain everything, entities can contain everything except graphs and variables can only contain variables and operators. Inputs and outputs can only contain entities.

Scopetor

Scopee

Graph

Entity

Variable

Operator

Graph

Entity

Variable/StaticVariable

Inputs/Outputs

Global and relative names#

The global name of a Scopee shows the full hierarchy of scope names separated by ‘/’ characters.

>>> g = Graph("g")
>>> with g:
>>>     e = Entity("e")
>>>     with e:
>>>         v = Variable("v")
>>>         with v:
>>>             w = StaticVariable("w")
>>> print(w.global_name)
g/e/v/w

The relative name is the difference between the global name of a (grand-)parent and a (grand-)child.

>>> e.get_get_relative_name("g/e/v/w")
v/w

To fo from global or relative name to object use the get_child_by_name method of scopetors.

>>> e.get_child_by_name("g/e/v/w", name_is_relative=False) is w
True
>>> g.get_child_by_name("v/w", name_is_relative=True) is w
True

Namespace convenience#

Halerium automatically puts references to children of a scopetor into the python namespace when the scope is entered and removes them if the scope is left.

g = Graph("g")
with g:
    Entity("e")

>>> with g:
>>>     print(e)
<HALerium.Entity at ...: name='e', global_name='g/e'>
>>> print(e)
NameError: name 'e' is not defined

This mechanism is applied as soon as a scopee is created.

>>> g = Graph("g")
>>> with g:
>>>     Entity("e")
>>>     print(e)
<HALerium.Entity at ...: name='e', global_name='g/e'>

Sibling references are removed when a deeper scope is entered

>>> g = Graph("g")
>>> with g:
>>>     Entity("e")
>>>     Entity("d")
>>>     with d:
>>>         print(g) # exists
>>>         print(d) # exists
>>>         print(e) # was removed when `d` was entered
<HALerium.Graph at ...: name='g', global_name='g'>
<HALerium.Entity at ...: name='d', global_name='g/d'>
NameError: name 'e' is not defined

and restored when the deeper scope is left.

>>> g = Graph("g")
>>> with g:
>>>     Entity("e")
>>>     Entity("d")
>>>     with d:
>>>         # print(e) # would cause a NameError
>>>     print(e) # works
<HALerium.Entity at ...: name='e', global_name='g/e'>

Within a scope the references will trump user assigned variables,

>>> g = Graph("g")
>>> with g:
>>>     e = 1.
>>>     Entity("e")
>>>     print(e)
<HALerium.Entity at ...: name='e', global_name='g/e'>

but as soon as the scope is left all user assigned variables are restored.

>>> g = Graph("g")
>>> with g:
>>>     e = 1.
>>>     Entity("e")
>>> print(e)
1.0

Remarks about namespace references#

Note that the references that halerium places into the namespace are not the actual objects, but special references to them. These references redirect all function calls etc. to the actual object. As a consequence the python built-ins is and type will distinguish between the actual object and the reference.

>>> g = Graph("g")
>>> with g:
>>>     Entity("e")
>>>     print(g.e is e)
False
>>> print(type(g.e))
<class 'HALerium.entity.entity.Entity'>
>>> with g:
>>>     print(type(e))
<class 'HALerium.scope.reference.EntityReference'>

For all other purposes the reference and the actual object are the same thing.