§1.2.2 Externalized roles

Normally, a team encapsulates its role against unwanted access from the outside. If roles are visible outside their enclosing team instance we speak of externalized roles.

Externalized roles are subject to specific typing rules in order to ensure, that role instances from different team instances cannot be mixed in inconsistent ways. In the presence of implicit inheritance (§1.3.1) inconsistencies could otherwise occur, which lead to typing errors that could only be detected at run-time. Externalized roles use the theory of "virtual classes" [1], or more specifically "family polymorphism" [2], in order to achieve the desired type safety. These theories use special forms of dependent types. Externalized roles have types that depend on a team instance.

§1.2.3 deduces even stronger forms of encapsulation from the rules about externalized roles.

(a) Visibility

Only instances of a public role class can ever be externalized.

(b) Declaration with anchored type

Outside a team role types are legal only if denoted relative to an existing team instance (further on called "anchored types"). The syntax is:

final MyTeam myTeam = expression;
RoleClass<@myTeam> role = expression;

The syntax Type<@anchor> is a special case of a parameterized type, more specifically a value dependent type (§9). The type argument (i.e., the expression after the at-sign) can be a simple name or a path. It must refer to an instance of a team class. The role type is said to be anchored to this team instance.
The type-part of this syntax (in front of the angle brackets) must be the simple name of a role type directly contained in the given team (including roles that are acquired by implicit inheritance).

Note:
Previous versions of the OTJLD used a different syntax for anchored types, where the role type was prefixed with the anchor expression, separated by a dot (anchor.Type, see §A.6.3). A compiler may still support that path syntax but it should be flagged as being deprecated.

(c) Immutable anchor

Anchoring the type of an externalized role to a team instance requires the team to be referenced by a variable which is marked final (i.e., immutable). The type anchor can be a path v.f1.f2... where v is any final variable and f1 ... are final fields.

(d) Implicit type anchors

The current team instance can be used as a default anchor for role types:

  1. In non-static team level methods role types are by default interpreted as anchored to this (referring to the team instance). I.e., the following two declarations express the same:
    public RoleX getRoleX (RoleY r) {  stmts  }
    public RoleX<@this> getRoleX (RoleY<@this> r) {  stmts  }
  2. In analogy, role methods use the enclosing team instance as the default anchor for any role types.

Note, that this and Outer.this are always final.
The compiler uses the pseudo identifier tthis to denote such implicit type anchors in error messages.

(e) Conformance

Conformance between two types RoleX<@teamA> and RoleY<@teamB> not only requires the role types to be compatible, but also the team instances to be provably the same object. The compiler must be able to statically analyze anchor identity.

(f) Substitutions for type anchors

Only two substitutions are considered for determining team identity:

  1. For type checking the application of team methods, this is substituted by the actual call target. For role methods a reference of the form Outer.this is substituted by the enclosing instance of the call target.
  2. Assignments from a final identifier to another final identifier are transitively followed, i.e., if t1, t2 are final, after an assignment t1=t2 the types R<@t1> and R<@t2> are considered identical. Otherwise R<@t1> and R<@t2> are incommensurable.
    Attaching an actual parameter to a formal parameter in a method call is also considered as an assignment with respect to this rule.

(g) Legal contexts

Anchored types for externalized roles may be used in the following contexts:

  1. Declaration of an attribute
  2. Declaration of a local variable
  3. Declaration of a parameter or result type of a method or constructor
  4. In the playedBy clause of a role class (see §2.1).

It is not legal to inherit from an anchored type, since this would require membership of the referenced team instance, which can only be achieved by class nesting.

Note:
Item 4. — within the given restriction — admits the case where the same class is a role of one team and the base class for the role of another team. Another form of nesting is defined in §1.5.

(h) Externalized creation

A role can be created as externalized using either of these equivalent forms:

outer.new Role()
new Role<@outer>()

This requires the enclosing instance outer to be declared final. The expression has the type Role<@outer> following the rules of externalized roles.
The type Role in this expression must be a simple (unqualified) name.

(i) No import

It is neither useful nor legal to import a role type.

Rationale:
Importing a type allows to use the unqualified name in situations that would otherwise require to use the fully qualified name, i.e., the type prefixed with its containing package and enclosing class. Roles, however are contained in a team instance. Outside their team, role types can only be accessed using an anchored type which uses a team instance to qualify the role type. Relative to this team anchor, roles are always denoted using their simple name, which makes importing roles useless.

A static import for a constant declared in a role is, however, legal.

Example code (Externalized Roles):
1
team class FlightBonus extends Bonus {
2
  public class Subscriber {
3
    void clearCredits() { ... }
4
  }
5
  void unsubscribe(Subscriber subscr) { ... }
6
}
7
class ClearAction extends Action {
8
  final FlightBonus context;
9
  Subscriber<@context> subscriber;
10
  ClearAction (final FlightBonus bonus, Subscriber<@bonus> subscr) {
11
    context = bonus; // unique assignment to 'context'
12
    subscriber = subscr;
13
  }
14
  void actionPerformed () {
15
    subscriber.clearCredits();
16
  }
17
  protected void finalize () {
18
    context.unsubscribe(subscriber);
19
  }
20
}
Effects:
  • Lines 1-6 show a terse extract of a published example [NODe02]. Here passengers can be subscribers in a flight bonus program.
  • Lines 7-20 show a sub-class of Action which is used to associate the action of resetting a subscriber's credits to a button or similar element in an application's GUI.
  • Attribute context (line 8) and parameter bonus (line 10) serve as anchor for the type of externalized roles.
  • Attribute subscriber (line 9) and parameter subscr (line 10) store a Subscriber role outside the FlightBonus team.
  • In order to type-check the assignment in line 12, the compiler has to ensure that the types of LHS and RHS are anchored to the same team instance. This can be verified by checking that both anchors are indeed final and prior to the role assignment a team assignment has taken place (line 11).
    Note, that the Java rules for definite assignments to final variables ensure that exactly one assignment to a variable occurs prior to its use as type anchor. No further checks are needed.
  • It is now legal to store this role reference and use it at some later point in time, e.g., for invoking method clearCredits (line 15). This method call is also an example for implicit team activation (§5.3.(b)).
  • Line 18 demonstrates how an externalized role can be passed to a team level method. The signature of unsubscribe is for this call expanded to
    void unsubscribe(Subscriber<@context> subscr)
    (by substituting the call target context for this). This proves identical types for actual and formal parameters.