Feature Visibility
Avail methods are very complex entities, potentially comprising many definitions, semantic restrictions, grammatical restrictions, and seals. In aggregate, these components are called method features (or just features for short).
The accessibility of a method is controlled entirely by the name resolution process[1]: if it is possible to resolve a name to the atom associated with the method, then it is possible to invoke or override the method. But the ability to access a method does not imply the ability to access all of its features.
A feature is available within a module's body if either of the following complex conditions are met:
- The name of the method is resolvable, the feature is defined within the body of the module, and the use of the feature occurs lexically after the definition of the feature.
- The name of the method is resolvable and the module imports, for private or public use, directly or indirectly, the upstream module that defined the feature.
The first condition is fairly straightforward, but the second condition warrants some elaboration. Consider the following quartet of modules, each of which overrides or sends "_added to_"
:
To the right is a pictorial description of the dependency graph of the modules (including one that will be introduced below). The solid line represents an import through an extended imports section. The dashed lines represent imports through private imports sections.
Passive Number Adder
introduces and exports "_added to_"
. It also provides the initial definition, one that accepts two "number"
s and answers their sum as a number
. Call this definition D0
.
Passive Integer Adder
imports and re-exports everything from Passive Number Adder
, and also adds a definition that specializes "_added to_"
to answer an "integer"
when the arguments are integer
s. Call this definition D1
.
Passive String Adder
imports from Passive Number Adder
, and adds a definition that operates on "string"
s, but does not re-export anything. Call this definition D2
.
Adder User
imports from both Passive Integer Adder
and Passive String Adder
. Its body includes three sends of "_added to_"
, one for each of the definitions D0
, D1
, and D2
, introduced in Passive Number Adder
, Passive Integer Adder
, and Passive String Adder
, respectively.
D1
is visible because 1) "_added to_"
is resolvable (thanks to Passive Integer Adder
re-exporting Passive Number Adder
) and 2) Adder User
imports Passive Integer Adder
.
D2
is visible because 1) "_added to_"
is resolvable and 2) Adder User
imports Passive String Adder
.
D0
is the most complex case. "_added to_"
is resolvable, so the precondition of method visibility is satisfied. But none of Adder User
's import targets introduce this definition. The definition is nonetheless visible because it is visible in an upstream module. In this case, D0
is visible in each of Passive Number Adder
, Passive Integer Adder
, and Passive String Adder
, all of which are upstream of Adder User
.
Now consider a final example:
Bad Adder User
imports from Passive Number Adder
and Passive String Adder
. Its body includes the same three sends of "_added to_"
that Adder User
included. But only D0
and D2
are visible. The invocation of D1
on line 39 is incorrect, as D1
is not visible — because Passive Integer Adder
is not upstream of Bad Adder User
.
[1] This is 99% true. Avail sports an extremely powerful reflective model, but balances it against sincerity of privacy. It is not possible to obtain through reflection access to a method that you could not have resolved at compile time. It is, however, possible to define a method M
that resolves names to local methods, and then export M
for use by downstream modules. In this way, it is possible for a module to gain access to a method that it cannot resolve directly — but the programmer has to specifically design this capability into the software.
‹ Name Resolution | | | Return to Modules | | | Module Life Cycle › |