§2.1 playedBy relation

(a) Role-base binding

Roles are bound to a base class by the playedBy keyword.

1
public team class MyTeamA {
2
  public class MyRole playedBy MyBase {
3
    ...
4
  }
5
}

(b) Inheritance

The playedBy relation is inherited along explicit and implicit (§1.3.1.(c)) role inheritance.

(c) Covariant refinement

An explicit sub-role (sub-class using extends) can refine the playedBy relation to a more specific base class (this is the basis for smart lifting (§2.3.3)).
If a role class inherits several playedBy relations from its super-class and its super-interfaces, there must be a most specific base-class among these relations, which is conform to all other base-classes. This most specific base-class is the base-class of the current role.

(d) No-variance

An implicit sub-role (according to §1.3.1.(c)) may only add a playedBy relation but never change an existing one.
Note however, that implicit inheritance may implicitly specialize an existing playedBy relation (this advanced situation is illustrated in §2.7.(d)).

(e) Use of playedBy bindings

The playedBy relation by itself has no effect on the behavior of role and base objects. It is, however, the precondition for translation polymorphism (lowering: §2.2 and lifting: §2.3) and for method bindings (callout: §3 and callin: §4).

(f) Effect on garbage collection

A role and its base object form one conceptual entity. The garbage collector will see a role and its base object as linked in a bidirectional manner. As a result, a role cannot be garbage collected if its base is still reachable and vice versa.
Internally a team manages its roles and corresponding bases using weak references. When using one of the getAllRoles(..) methods (see §6.1.(a)), the result may be non-deterministic because these internal structures may hold weak references to objects that will be collected by the next run of the garbage collector. We advise clients of getAllRoles(..) to call System.gc() prior to calling getAllRoles(..) in order to ensure deterministic results.

§2.1.1 Binding interfaces↑ §2.1

Role base bindings may involve classes and/or interfaces. An interface defined as a member of a team is a role interface and may therefore have a playedBy clause. Also the type mentioned after the playedBy keyword may be an interface.

Implementation limitation:
The language implementation as of OTDT version 2.0 imposes one particular restriction when binding a role to a base interface: A role binding to a base interface may not contain any callin bindings (§4).

§2.1.2 Legal base classes↑ §2.1

Generally, the base class mentioned after playedBy must be visible in the enclosing scope (see below (§2.1.2.(c)) for an exception). Normally, this scope is defined just by the imports of the enclosing team. For role files (§1.2.5.(b)) also additional imports in the role file are considered.
§2.1.2.(d) below defines how imports can be constrained so that certain types can be used as base types, only.

(a) No role of the same team

The base class of any role class must not be a role of the same team.
It is also not allowed to declare a role class of the same name as a base class bound to this or another role of the enclosing team, if that base class is given with its simple name and resolved using a regular import. Put differently, a base class mentioned after playedBy may not be shadowed by any role class of the enclosing team.
Base imports as defined below (§2.1.2.(d)) relax this rule by allowing to import a class as a base class only. In that case no shadowing occurs since the scopes for base classes and roles are disjoint.

(b) Cycles

The base class mentioned after playedBy should normally not be an enclosing type (at any depth) of the role class being defined.
This rule discourages the creation of cycles where the base instance of a given role R contains roles of the same type R.
More generally this concerns any sequence of classes C1, C2, .. Cn were each Ci+1 is either a member or the base class of Ci and Cn = C1.
Such structures may be difficult to understand and have certain restrictions regarding callout (§3.1.(a)) and base constructor calls (§2.4.2). It is furthermore recommended to equip all roles that are played by an enclosing class with a guard predicate (§5.4) like this:

base when (MyTeam.this == base)

This will avoid that the role adapts other instances of the enclosing class which are not the enclosing instance.

It is prohibited to bind a role class to its own inner class.

(c) Base class decapsulation

If a base class referenced after playedBy exists but is not visible under normal visibility rules of Java, this restriction may be overridden. This concept is called decapsulation, i.e., the opposite of encapsulation (see also §3.4). A compiler should signal any occurrence of base class decapsulation. If a compiler supports to configure warnings this may be used to let the user choose to (a) ignore base class decapsulation, (b) treat it as a warning or even (c) treat it as an error.

Binding to a final base class is also considered as decapsulation, since a playedBy relationship has powers similar to an extends relationship, which is prohibited by marking a class as final.

Decapsulation is not allowed if the base class is a confined role (see §7.2).

Within the current role a decapsulated base class can be mentioned in the right-hand-side of any method binding (callout (§3) or callin (§4)). Also arguments in these positions are allowed to mention the decapsulated base class:

(d) Base imports

If the main type in a file denotes a team, the modifier base can be applied to an import in order to specify that this type should be imported for application as a base type only. Example:

1
import base some.pack.MyBase;
2
public team class MyTeam {
3
  // simple name resolves to imported class:
4
  protected class MyRole playedBy MyBase { } 
5
  MyBase illegalDeclaration; // base import does not apply for this position
6
}

Types imported by a base import can only be used in the same positions where also base class decapsulation (§2.1.2.(c)) is applicable.
It is recommended that a type mentioned after the keyword playedBy is always imported with the base modifier, otherwise the compiler will give a warning.
Base imports create a scope that is disjoint from the normal scope. Thus, names that are imported as base will never clash with normally visible names (in contrast to §1.4). More specifically, it is not a problem to use a base class's name also for its role if a base import is used.

(e) No free type parameters

Neither the role class nor the base class in a playedBy binding must have any free type parameters. If both classes are specified with a type parameter of the same name, both parameters are identified and are not considered as free.

From this follows that a role class cannot have more type parameters than its base. Conversely, only one situation exists where a base class can have more type parameters than a role class bound to it: if the role class has no type parameters a generic base class can be bound using the base class's raw type, i.e., without specifying type arguments.

Note:
The information from the playedBy declaration is used at run-time to associate role instances to base instances. Specifying a base class with free type parameters would imply that only such base instances are decorated by a role whose type is conform to the specified parameterized class. However, type arguments are not available at run-time, thus the run-time environment is not able to decide which base instances should have a role and which should not. This is due to the design of generics in Java which are realized by erasure.

The following example shows how generics can be used in various positions. Note, that some of the concepts used in the example will be explained in later sections.

1
public class ValueTrafo<T> {
2
  public T transform(T val) throws Exception { /* ... */ }
3
}
4
public team class TransformTeam {
5
    protected class SafeTrafo<U> playedBy ValueTrafo<U> {
6
        U transform(U v) -> U transform(U val); 
7
        protected U safeTransform(U v) {
8
            try {
9
            	return transform(v);
10
            } catch (Exception e) {
11
            	return v;
12
            }
13
        }
14
    }
15
    <V> V perform(ValueTrafo<V> as SafeTrafo<V> trafo, V value) {
16
        return trafo.safeTransform(value);
17
    } 
18
}
19
...
20
ValueTrafo<String> trafo = new ValueTrafo<String>();
21
TransformTeam safeTrafo = new TransformTeam();
22
String s = safeTrafo.perform(trafo, "Testing");
23
Explanation
  • Line 5 shows a role with type parameter U where the type parameter is identified with the corresponding type parameter of the role's base class (which is originally declared as T in line 1.
  • Line 6 shows a callout binding (§3) which mappes a base method to a corresponding role method while maintaining the flexible typing.
  • The regular method in lines 7-13 just passes values of type U around.
  • The generic method in line 15 ff. uses declared lifting (§2.3.2) to obtain a role for a given base object. The method has no knowledge about the concrete type arguments of either role nor base, but works under the guarantee that both type arguments will be the same for any single invocation.
  • Lines 20 ff. finally create instances of base and team and invoke the behavior thereby instantiating type parameters to String.