The Avail Programming Language
Mobile Users: Click here to view our text rendering warning.

Name Resolution

A Venn diagram showing that the 'Uses' section is an import section, the 'Names' section is an export section, and the 'Extends' section is both an import and export section.
The flow of names into, out of, and through an Avail module is controlled by its header sections.

Names are the currency of modules. Names flow into modules through private imports and extended imports sections. Names flow out of modules through introduced names and extended imports sections.

Names are not constrained to be globally unique within a project, as this would be a heinous requirement. Even following perfectly reasonable (but not Procrustean) naming conventions, it is extremely likely that unaffiliated vendors would introduce name collisions. Despite having the ability to import a name under a new name, a programmer would find name selection an onerous task.

Every name is associated with an atom that is created at the point of the name's original introduction. Think of an atom as a discrete particle of identity. Most values in Avail are identityless, so atoms are interesting just by dint of having identity. As such, it is occasionally useful to resolve a name to an atom for no other reason than to obtain the atom. It is more often the case, however, that a dependent module imports a name because it wishes to use the associated method in its body.

There are only two ways to introduce a name:

  1. By mentioning the name, as a string literal, in an introduced names section.
  2. By resolving a name to an atom when no atom in scope corresponds to that name. All names introduced in this fashion are private to the resolving module (unless dynamically exported with "Export_as a new name").

There are two ways to reference a name within a module body:

  1. By sending a message that resolves a name to an atom, such as "Method_is_" or "atom for_", to a string that represents a name.
  2. By using the name to send a message. In this case, the compiler parses the source text to identify the names that correspond to expressions. For example, the compiler parses 1 + 3 to discover a send of "_+_". Once the name has been determined, the compiler uses available type information to resolve it to an atom.

Module resolution is the process by which names are resolved to atoms.

Name resolution The name resolution process. stringProvided Name Provided As String lookup Look Up Atom stringProvided->lookup parse Name Sent As Message parse->lookup create Create a New Atom lookup->create 0 isNew Created Locally? lookup->isNew ≥ 1 resolved Resolved create->resolved howMany Count Atoms isNew->howMany no isNew->resolved yes typeInfo Types Available? howMany->typeInfo ≥ 2 howMany->resolved 1 filter Filter by Argument Type typeInfo->filter yes ambiguous Ambiguous typeInfo->ambiguous no filter->resolved 1 unresolvable Unresolvable filter->unresolvable 0 filter->ambiguous ≥ 2
The name to resolve is provided as a string to name-resolving method.
In this case, name resolution begins with a send of a name-resolving method to a string that represents the name to resolve. Let us call this name N. Name resolution proceeds by looking up N in the resolving module.
The name to resolve is sent as a message.
In this case, name resolution begins with a send of a message. The compiler determines which message is being sent by parsing the related expression. Let us call this message N. Name resolution proceeds by looking up N in the resolving module.
Look up the atoms associated with the name.
Look up any atoms associated with N in the resolving module. These include any atoms 1) created by including a name in the introduced names section, 2) imported from an upstream module, or 3) created by a previous lookup conducted by the resolving module. Let C denote the set of candidate atoms:
C ≝ {A0, A1, …, An},
where A0 through An are the candidate atoms. Decide how to proceed based on the cardinality of C:
  • |C| = 0: Create a new atom that is private to the resolving module.
  • |C| ≠ 0: Determine whether any of the candidate atoms was created locally.
Create a new private atom.
N does not refer to an existing atom. Create a new atom, A, and associate it with N. This atom will remain private to the resolving module unless it is dynamically exported by "Export_as a new name". Name resolution has succeeded: N is resolved to A.
Is the name associated with a locally defined atom?
Does N refer to an atom Ai that was created by the resolving module? If so, then name resolution has succeeded: resolve N to Ai, even if there are other candidate atoms, i.e., C ≠ {Ai}. If not, then pay closer attention to the cardinality of C.
Just how many atoms are associated with the name?
Because none of A0An are locally defined atoms, re-examine the cardinality of C:
  • |C| = 1: Only one imported atom, A0, is named N, therefore C is
    C ≝ {A0}.
    Name resolution has succeeded: N is resolved to A0.
  • |C| ≥ 2: N is a viable name for any element of C, so N has surface ambiguity. It may be possible to use type information to further disambiguate N.
Is type information available to disambiguate the name?
Is any argument type information available to assist in the disambiguation of N? Such information is only available when N is being sent as a message. If argument type information is available, then filter the choice of atom based on the visibility of method definitions. If not, then name resolution has failed: N is semantically ambiguous.
Use type information to filter the candidate atoms.
Method arguments are statically typed. Use this type information to determine which visible method definitions are applicable at the call site of N. Let C′ be the set of atoms that correspond to methods with applicable definitions. The expansion of C′ is
C′ ≝ {Ai, Aj, Ak, …},
where Ai, Aj, Ak, etc., are elements of C. Decide how to proceed based on the cardinality of C′:
  • |C′| = 0: No visible method definitions accept the supplied argument types. Name resolution has failed: N is unresolvable.
  • |C′| = 1: Exactly one visible method definition accepts the supplied argument types, therefore C′ is
    C′ ≝ {Ai}.
    Name resolution has succeeded: N is resolved to Ai.
  • |C′| = 2: Multiple method definitions accept the supplied argument types, i.e., the available type information was insufficient to uniquely disambiguate N. Name resolution has failed: N is semantically ambiguous.

Consider the following example:

The module Summation imports the name "_+_" from each of Avail and Dimensional Analysis. Each of these modules separately defines an atom named "_+_". This module is nonetheless valid, because the resolution of "_+_" to an atom is never ambiguous. On line 48, the arguments of "_+_" are known to be numbers, which unambiguously identifies the method (and therefore the associated atom) to be the "_+_" introduced and exported by Avail. On line 56, each argument is a dimensioned quantity, so the correct "_+_" is the one introduced and exported by "Dimensional Analysis".

Now consider another (contrived) case, where the programmer wants to override "_+_" with new behavior.

In Bad String Addition, the method "_+_" is overridden with a new definition, one sufficient to concatenate two strings. But which "_+_" should be overridden? There are two in scope: one from Avail and one from Dimensional Analysis. In the previous example, the code was sending "_+_", so the types of the arguments were available to assist in name resolution. But in this example, no such information is available; the method definer, "Method_is_", has only the lexical name of the desired atom to guide it, and there are two visible atoms with the same lexical name. Compilation will therefore fail as a direct consequence of name resolution failure.

‹ Module Resolution | Return to Modules | Name Resolution ›