A foundation for trait-based

John Reppy Aaron Turon University of Chicago {jhr, adrassi}@cs.uchicago.edu

Abstract We present a calculus, based on the Fisher-Reppy polymorphic Scharli¨ et al. introduced traits as reusable units of behavior inde- trait calculus [FR03], with support for trait privacy, hiding and deep pendent of the inheritance hierarchy. Despite their relative simplic- renaming of trait methods, and a more granular trait typing. Our ity, traits offer a surprisingly rich calculus. Trait calculi typically in- calculus is more expressive (it provides new forms of conflict- clude operations for resolving conflicts when composing two traits. resolution) and more flexible (it allows after-the-fact renaming) In the existing work on traits, these operations (method exclusion than the previous work. Traits provide a useful mechanism for shar- and aliasing) are shallow, i.e., they have no effect on the body of the ing code between otherwise unrelated classes. By adding deep re- other methods in the trait. In this paper, we present a new trait sys- naming, our trait calculus supports sharing code between methods. tem, based on the Fisher-Reppy trait calculus, that adds deep oper- For example, the JAVA notion of synchronized methods can im- ations (method hiding and renaming) to support conflict resolution. plemented as a trait in our system and can be applied to multiple The proposed operations are deep in the sense that they preserve methods in the same class to produce synchronized versions. We any existing connections between the affected method and the other term this new use of traits trait-based metaprogramming. methods of the trait. Our system uses Riecke-Stone dictionaries to We review the standard trait operations in Section 2 and de- support these features. In addition, we define a more fine-grained scribe our additions in Section 3; the presentation is organized mechanism for tracking trait types than in previous systems. The around a series of examples, culminating with the introduction of resulting calculus is more flexible and expressive, and can serve as our metaprogramming idiom. The formal description of our pro- the foundation for trait-based metaprogramming, an idiom we in- posal begins with Section 4, which outlines our notation and the troduce. A companion technical report proves type soundness for syntax of the calculus. Section 5 describes the static semantics. The our system; we state the key results in this paper. system types traits at the time of trait formation, rather than trait in- lining. It improves on previous work by tracking trait requirements at a per-method, rather than per-trait basis, which allows spurious 1. Introduction requirements to be dropped as a trait is manipulated. Properly han- dling evaluation in the presence of privacy can be somewhat subtle. A trait is a simple collection of methods that represent the partial We utilize Riecke-Stone dictionaries [RS02] to handle privacy; this implementation of a class. Methods defined in a trait are provided technique also provides support for renaming. Our approach is de- methods; any method referenced in a trait but not provided by it tailed in Section 6. Finally, we outline the proof of type soundness is a required method. Traits are similar to abstract classes, but with in Section 7. After the formal description, we take stock of the cal- two important differences: traits cannot introduce state, and they do culus in a broader context. Section 8 details other work in traits and not lie within the inheritance hierarchy. The primary mechanism of relates our presentation of traits to other constructs, e.g. mixins and class reuse is inheritance, which is an asymmetric operation. With aspects. The calculus raises some interesting questions for language traits, reuse takes place via composition, a symmetric concatena- design. We examine these questions and conclude in Section 9. tion of two traits. In addition to composition, trait calculi include A technical report version of this paper includes the complete fine-grained operations for manipulating traits as method suites. Ul- formal model and a detailed proof of type soundness [RT06]. timately, traits are inlined into classes during class formation. Un- fulfilled trait requirements must be provided by the class at the time of inlining. 2. Traits Traits were introduced by Scharli¨ et al. in the setting of Traits originate from the observation that classes serve two often [SDNB03]. A companion paper explored the use of conflicting purposes [SDNB03]. From one perspective, classes are traits to refactor the SMALLTALK collection classes, with encourag- meant to generate objects, which means they must be complete and ing results: a 10% reduction in method count for a 12% reduction in monolithic. At the same time, classes act as units of reuse via inher- overall code size [BSD03]. Since SMALLTALK is untyped, the orig- itance, and from this perspective they should be small, fine-grained, inal work on traits did not include a . Fisher and Reppy and possibly incomplete. Inheritance must straddle these two roles, gave a calculus for statically-typed traits [FR04, FR03]. Other work which often forces the designer of a class hierarchy to choose be- subsequently developed typed trait calculi for JAVA [LS04, SD05]. tween cleanliness and implementation cleanliness. Quitslund performed a simple analysis of the Swing Java , Consider the case of two classes in different subtrees of the in- which suggests that can also be improved by adding heritance hierarchy which both implement some common protocol. traits to Java [Qui04]. A fair amount of activity has followed, with To avoid code duplication, the protocol implementation should be trait implementations underway or completed for #, JAVA,, 1 shared between the two classes. In a single inheritance framework, and SCALA. the common code can be lifted to a shared superclass, but doing so pollutes the interface of the superclass, affecting all of its sub- 1 See http://www.iam.unibe.ch/∼scg/Research/Traits/ classes. With , the protocol implementation can for more information. reside in a new superclass that is inherited along with the existing superclasses, but multiple inheritance complicates the implementa- trait TIntRd trait TIntWr class CDevice tion of subclasses (e.g., with respect to instance variables). required required field desc field desc field desc provided provided class CDevice method Rd method Wr field desc

class CIntRd class CIntRW class CIntWr class CIntRd class CIntWr field desc field desc field desc field desc field desc method Rd method Rd method Wr method Rd method Wr method Wr

trait TSRd trait TSWr required required class CIntRW field lock field lock field desc provided provided method Rd override Rd override Wr method Wr class CSIntRd class CSIntWr field lock field lock field desc field desc class CSIntRW override Rd override Wr class CSIntRd field lock class CSIntWr field lock field desc field lock field desc method Rd field desc inheritance method Rd method Wr method Wr class CSIntRW field lock field desc missed trait override Rd inheritance inheritance inlining override Wr

Figure 1. Synchronized readers and writers in a single inheritance Figure 2. Using traits to implement synchronized readers and writ- framework ers

Figure 1 illustrates these issues with a concrete class hierarchy in a single inheritance framework. The root of the hierarchy is the CDevice2 class, which implements I/O on a file descriptor. It has two subclasses for, respectively, reading and writing integers on the device. Defining a class that supports both reading and writing These properties make traits more nimble and lighter-weight than (CIntRW) requires reimplementing one of the methods (denoted either multiple inheritance [Str94] or mixins [BC90, FKF98]. Rd by the dashed arrow in the figure). While we could lift the Two traits can be combined via trait composition, written T1 + Wr CDevice and methods to the class, doing so would pollute the T2. The resulting trait is a flat merger of the operands. Compos- interface of the original CIntRd and CIntWr subclasses as well ing two traits may fulfill method requirements for one or both as new subclasses such as boolean readers and writers. The class of the traits. In a statically-typed setting, composition requires its hierarchy is further extended with support for synchronized reading operands to be disjoint; method conflicts must be resolved explic- and writing by adding a lock. Single inheritance again forces a itly, using other trait operations. Under this definition, trait com- reimplementation of methods. position is commutative, obviating the need for a linear ordering In contrast to inheritance, which specifies the relationship be- found with single inheritance mixins. tween a family of classes, traits allow the implementation of a sin- Trait composition is a symmetric alternative to reuse via inher- gle class to be factored into multiple, structured parts. Classes are itance. By defining additional operations on traits, one can capture retained as hierarchically organized object generators, but traits are more complex idioms of reuse. The typical suite of trait operations introduced as flatly organized, partial class implementations. Traits include alias and exclude, which are useful for conflict resolution. thus assist in separating the roles distinguished above. Figure 2 As an illustration, assume that we have two traits, TPoint and shows how traits can allow code reuse without having to define TColored, which both provide a toString method, and that methods too high in the hierarchy; in this example, four traits are we wish to compose them into a single trait, TCPoint. In the sim- used to generate six classes. Note that the traits in the example have plest case, we can resolve such a conflict by choosing one method 3 field requirements, not just method requirements. The override or the other. We exclude the unwanted method implementation, and annotation on provided methods signifies that the method invokes compose the resulting trait: the super-method with the same name. TCPoint = TPoint + (TColored exclude toString) Operations on traits TCPoint will provide a single toString method, using the implementation from TPoint. In addition, any invocations of Traits are partial class implementations, but they are restricted to toString from within other methods provided by TColored providing only a simple set of methods. In particular, traits cannot will now invoke TPoint’s implementation, making the exclude- introduce state. The methods of a trait are only loosely coupled: compose combination similar to method override. they can be freely removed and replaced by other implementations. Sometimes it is useful to retain both implementations of a con- flicting method, perhaps providing a new implementation combin- 2 For concrete names, we prefix classes with C and traits with . ing the two. The alias operation creates a new name for an existing 3 Field requirements were introduced in [FR04]. method: TCPoint = { extended with another implementation of toString, even one provides toString() : string { with a different type. self.strP() + ": " + self.strC(); Of course, renaming can be used alone in order to align a } “misnamed” provided method with an existing class hierarchy or } + ((TPoint alias toString as strP) signature constraint. Required methods may be renamed for the exclude toString) + ((TColored alias toString as strC) same reason. Furthermore, super-method names may be renamed, exclude toString) so that for example all the invocations of super.foo within a trait may be renamed to invoke super.bar. The rationale for strP strC It is important to realize that, while and are avail- this last form of renaming will become more clear in the following TCPoint toString able in this version of , the original meth- discussion. ods have effectively been overridden. Invocations of toString from the other methods provided by TPoint and TColored will Trait-based metaprogramming use the new, combined toString implementation provided by TCPoint. The combination of aliasing and excluding thus yields Scharli¨ et al. summarized their system with the following equa- a shallow renaming: existing references to an aliased method con- tion [SDNB03]: tinue to refer to the original method name. Class = Superclass + State + Traits + Glue

3. Traits with hiding and deep renaming A trait represents a flat fragment of a class’s behavior, and class behavior is closely tied to naming. But a trait can also be seen We extend the trait system of the previous section with two new as a collection of named provided methods parameterized over trait operations, hide and rename, which act as the deep variants a collection of named required methods (and fields). Thus, traits of exclude and alias. These two operations provide new forms of capture a relationship between two families of named methods. conflict resolution when composing traits. Each is also useful in Often this relationship carries as much information as the names isolation: hiding without composition yields trait privacy, while of the methods themselves. renaming yields an idiom we term trait-based metaprogramming, Reconsider the example of traits usage presented in Figure 2. the most exciting aspect of our work. The trait TIntRd requires a field desc and provides a method The hide operation permanently binds a provided method to a Rd. In a broad sense, it does not matter what we call these entities trait, while hiding the method’s name. A new method with the same (Rd, read, Read, etc.), so long as (1) the names convey “descrip- name can be introduced as a new provided or required method of tor” and “read,” respectively, and (2) the relationship between the the trait, but existing references to the method from other provided entities remains intact. After-the-fact renaming allows the names to methods are statically bound to its implementation at the time of be changed as needed. hiding. Returning to the TCPoint example, we can write But what can we say about the traits TSRd and TSWr from the TCPoint = TPoint + (TColored hide toString) same example? Each requires the field lock and provides a single Here, TCPoint will provide the toString implementation method that wraps a super-method invocation with synchronization from TPoint. Unlike the exclusion example, any references from code. For these traits, the relationship between the provided meth- within TColored to the toString method will continue to ods and the trait’s requirements carries essentially all of the relevant Rd Wr refer to TColored’s implementation. In effect, TColored’s information; the provided method names and are incidental. TSync toString implementation is provided by TCPoint, but in a We can capture the relationship in a single trait, , which is nameless and inaccessible form. Where combining exclusion and parameterized by type: composition leads to overriding, the combination of hiding and trait TSync = { composition yields shadowing. provides Op(x : ty1) : ty2 { Method hiding can also be used to hide trait implementation self.lock.Acquire(); super.Op(x) before details — i.e., as a form of trait privacy. A trait implements some self.lock.Release(); collection of behavior, and it may need to make use of new methods } that are specific to its implementation but are not appropriate to requires field lock : LockObj provide publicly. Although such “helper methods” could be made } private after they are inlined into a class, we believe that traits The generic method Op and its associated super-send can be re- should not only factor out, but also encapsulate, units of behavior. named and used to override a method with a synchronized wrapper. The importance of trait privacy will depend to some extent on the Returning to the readers and writers example, we could rename Op strength of the surrounding language features. In a language with a to Rd and super.Op to super.Rd, and likewise for Wr. The powerful module system, for example, it is likely that trait privacy key insight is that TSync can be instantiated several times, once could be achieved through signature ascription instead [FR99]. In for each method we want to synchronize. Figure 3 shows the read- any case, trait privacy is a free by-product of introducing method ers/writers example using TSync; we now have six classes derived hiding for conflict resolution. from three traits. It is worth noting that, with the ability to rename Loosening the connection between method names and method field requirements, different instantiations of TSync could use dif- implementations suggests the possibility of a deep renaming oper- ferent locks. Our model does not support field renaming, but this ation, as opposed to shallow renaming with alias-exclude. As with is for simplicity only; the feature would be a fairly straightforward the other operations, pairing renaming with composition provides addition. a new form of conflict resolution: Traits were intended to capture specific, named behavior at the TCPoint = (TPoint rename toString to strP) class level. Examples like TSync shift the focus to behavior at the + (TColored rename toString to strC) method family level. With the latter perspective it becomes sensi- This version of TCPoint does not provide a toString method ble to inline a trait several times into a class; we label this idiom at all. Existing references to toString from within TPoint and trait-based metaprogramming. The power of the technique is that TColored now refer to strP and strC, respectively; this is we can freely mix flat requirements for the class (e.g., lock) with the sense in with the renaming is “deep.” TCPoint can now be provided methods that may be instantiated several times. Traits trait TIntRd trait TIntWr class CDevice P ::= ; P | e program required required field desc field desc field desc provided provided D ::= t = (~α)T trait declaration method Rd method Wr | c = C class declaration | x = e expression declaration

class CIntRd class CIntRW class CIntWr T ::= t(~τ) polymorphic trait name field desc field desc field desc | h| µ; ρ |i trait formation method Rd method Rd method Wr | T1 + T2 trait composition method Wr | T exclude m method exclusion | T hide m method hiding trait TSync | T alias m as m0 method aliasing required 0 field lock | T rename to r method renaming provided µ ::= m (x : τ1): τ2 {e} method override Op r∈R ρ ::= hhr : τr ii inlining assumptions Wr/Op m∈M θ ::= h| m : τm@ρm |i trait type Rd/Op Rd/Op | Λ(~α).θ polymorphic trait type Wr/Op

class CSIntRd class CSIntRW class CSIntWr C ::= c class name field lock field lock field lock | nil empty class field desc field desc field desc | I in T extends C subclass formation method Rd method Rd method Wr method Wr I ::= λ(x : τ).(super e1) ⊕ e2 constructor l∈L χ ::= τ → {| l : τl |} class type trait trait inlining inheritance inlining with renaming e ::= x variable | λ(x : τ).e function abstraction | e1 e2 function application Figure 3. Using traits with renaming to implement synchronized | new C e object instantiation readers and writers | self host object | super.m super-method dispatch | e.m method dispatch provide the structure for specifying method relationships, and re- | e.f field selection naming provides the flexibility to apply traits in multiple contexts | e1.f := e2 field update f∈F within a single class. | {f = ef } field record The metaprogramming technique we have outlined is somewhat | e1 ⊕ e2 field concatenation ad hoc: examples like TSync would be best served by an explicit | () unit value notion of method name abstraction. A good starting point would τ ::= α type variable be a “lambda calculus of traits,” with method renaming playing l∈L | hl : τl i object type the role of substitution. It is easy to imagine further extensions. | τ1 → τ2 function type A sophisticated trait-metalanguage could codify certain patterns of f∈F | {f : τf } field record type trait use, perhaps providing a mechanism like JAVA’s synchronized | unit unit type keyword for appropriately instantiating traits like TSync. Borrow- ing a line from aspect-oriented programming, a language might also allow join points to be specified for trait application. Such pos- Figure 4. Trait calculus syntax sibilities are exciting and deserve to be explored, but before we can define abstraction mechanisms for higher-level calculi, we need a well-defined notion of substitution. Items ∈ Sets ⊂ Universe Starting with the next section, we give a detailed semantics of Field names f ∈ F ⊂ FU a calculus with hiding and renaming for traits, which can serve as Method names m ∈ M ⊂ M the foundation for trait-based metaprogramming. U Labels l ∈ L ⊂ LU = FU ∪ MU Super methods super.m ∈ S ⊂ SU 4. A trait calculus with hiding and renaming Requirements r ∈ R ⊂ RU = LU ∪ SU Slots i ∈ I ⊂ I To model our proposed features, we have developed a statically- U Variables x ∈ VARS typed trait calculus loosely based on Fisher and Reppy’s [FR03]. Type vars α ∈ TYVARS The calculus is meant to give a rigorous semantics of the new Trait names t ∈ TRAITNAMES trait operations, and does not represent a surface language design. Class names c ∈ CLASSNAMES We model the trait language in considerable detail, but restrict the rest of the language to essential features. Specifically, our class Figure 5. Naming conventions definition form does not support definition of methods directly. Instead, we leave this responsibility to traits. This choice is purely to minimize the complexity of the formal system; method definition in classes could be easily added. as shown in Figure 5. This convention allows entities to be distin- The syntax for our calculus is given in Figure 4. To keep the guished by the “type” of their name, without any additional book- syntax lightweight, we separate names into disjoint “universes,” keeping. In our model, a program is a series of zero or more declarations We also make heavy use of well-formedness checks, which take the followed by an expression. The calculus is organized into three form Γ `  ok where  is a type form, or Γ ` ok to assert that the components: a trait language, a class language, and an expression context Γ is well-formed. language. The trait language includes expressions forms for all of Although traits can be viewed as sophisticated syntactic sugar the features we have discussed: composition of traits, and exclu- that is “flattened” to a core class-based language [NDS06], there are sion, hiding, aliasing, and renaming of methods. The declaration advantages to recognizing traits directly. For the static semantics, form for traits allows parameterization by type variables. Traits are giving types to traits allows the detection of a number of errors initially formed with a single method rather than a method suite, during trait manipulation that would otherwise not be detected until which simplifies the semantics; a trait with multiple methods can trait inlining; it also makes separate compilation of traits possible. be constructed via repeated composition. In order to typecheck a trait, we must know the types of all The class language provides a simple model of single inheri- of its provided methods and of the self-methods, super-methods, tance with no visibility control. The calculus has an empty base and fields that it mentions. This type information amounts to a class, nil; all other classes must be defined via inheritance. Sub- collection of assumptions about (or constraints on) the classes in class formation takes a super class, a trait expression, and a con- which the trait will be inlined. The assumptions are guaranteed structor. In our model, the only methods introduced in a subclass to hold of well-typed provided methods, which are syntactically are those provided by the trait; there is no separate notion of method required to specify their own type. The remaining assumptions definition for classes. In particular, this means that any required constitute requirements of the trait, which fall into two categories: methods of the trait must be provided by the super class. We will 1. Self-method requirements, which can be fulfilled via trait con- sometimes refer to subclass formation as trait inlining to emphasize catenation or trait method aliasing. the role that traits play. When the trait being inlined is the result of simple trait formation and concatenation, trait inlining reduces to 2. Super-method and field requirements, which can only be ful- standard single inheritance. filled when inlining a trait. Subclass formation also allows the introduction of state via A straightforward type system might structure trait types as a a constructor function. State is restricted to field records which collection of typed labels, some of which are marked as required; can be concatenated using the ⊕ operator. Class constructors are this is the approach of [FR03]. Unfortunately, this view of trait syntactically constrained to apply the super class constructor to types forces a conservative typing of method exclusion. Consider an expression, and concatenate the result with a new field record. the trait TFoo: Fields referenced in an inlined trait may originate from either the trait TFoo = { super class or the newly formed subclass. provides A() : unit { print("Hello!"); } The expression language is a simple object calculus with first provides B() : unit { self.A(); self.C(); } class functions. It has imperative features (object instantiation and requires C : unit -> unit field update) which allow us to put the class language to work in a } realistic way. The trait TFoo exclude A should require both A and C, while We will need several notational tools: we write A t B for TFoo exclude B should not have any requirements. But a flat A ∩ B = ∅; when f is a function, f[x 7→ y] denotes the function trait type assigning types to labels gives no way to distinguish that takes x to y and otherwise behaves the same as f; the notation between excluding A and excluding B; whenever a method is ex- f ↓ A yields the restriction of the function f to the domain A; cluded, it must be conservatively counted as a required method be- the notation f \ x is shorthand for f ↓ (dom(f) \{x}) or, if x cause it may be invoked from another provided method. is a set, f ↓ (dom(f) \ x). We make heavy use of notation like y∈Y To overcome this limitation, we track requirements on a per- {xy } to describe a collection of elements xy indexed by a set provided-method, rather than per-trait, basis. The requirements for Y . Such notation allows us to give types to a collection of labels, l∈L a provided method are collected into a set of inlining assump- e.g. hl : τl i. We define the binary operator ], as follows: tions ρ that represent that method’s view of any class that inlines 0 the trait. To distinguish between super-method and self-method re- τl = τl for all l ∈ L1 ∩ L2 quirements, we have a universe of super-method names, SU ; for l∈L1 0 l∈L2 l∈L1 0 l∈L2 {l : τl }]{l : τl } = {l : τl , l : τl } any s ∈ SU there is a unique m ∈ MU such that s = super.m. A method may require both m and super.m, but for the two re- The ] operator joins two possibly overlapping label/type collec- quirements to be coherent, their types must be compatible. Since tions. It is only defined when the operands agree on the type of our calculus only supports width subtyping, an overriding method any shared labels. While we group different label/type collections must have the same type as its corresponding super-method. This in syntactically distinct categories (e.g., trait types versus object condition is reflected in the well-formedness judgment for inlining types), we freely use ] to join two or more such collections in the assumptions: same syntactic category. Γ ` τr ok for all r ∈ R τm = τsuper.m for all m with m ∈ R, super.m ∈ R r∈R 5. Static semantics Γ ` hhr : τr ii ok Typing judgments in our calculus are written in terms of an ordered Inlining assumptions can be seen as a compact and uniform rep- context Γ. Types can inhabit one of three syntactic categories: trait resentation of a pair of object types, τsuper and τself , which are su- types θ, class types χ, or expression types τ. The most important pertypes of the eventual super- and self-object types. To transform judgment forms are the following: a set of inlining assumptions into its corresponding object types, we have the functions super and self: Γ ` T : θ T θ r∈R super.m∈R trait has type super hhr : τr ii = hm : τsuper.m i Γ ` C : χ class C has type χ r∈R l∈R∩LU self hhr : τr ii = hl : τl i Γ ` e : τ expression e has type τ Γ ` µ : τ method µ has type τ A trait type θ is a labeled collection of method types with Γ ` τ1 <: τ2 τ1 is a subtype of τ2 inlining assumptions. A trait formation expression provides a single method and the initial inlining assumptions for that method. To type provided method: trait formation, we ensure that the given inlining assumptions are m∈M Γ ` T : h| m : τm@ρm |i well-formed and that the given method can be typed under them: 0 0 r, r ∈/ FU r ∈/ M r ∈ ρm for some ρm ∈ M 0 m∈M 0 µ = m (x : τ1): τ2 {e} Γ ` ρ ] hhm : τii ok θ = h| m : τm@(ρm[r /r]) |i[r /r]Γ ` θ ok Γ, super : super(ρ), self : self(ρ) ` µ : τ Γ ` T rename r to r0 : θ 0 Γ ` h| µ; ρ |i : h| m : τm@ρ |i Typing method hiding is somewhat challenging for our system. Notice that a recursive method must specify its own type in its We want to completely remove any mention of a method m0 that inlining assumptions. The ] operator ensures that this type agrees is to be hidden, so that its name may be reused (possibly at a with the method’s actual type. different type). But if we simply remove m0 from the trait type, its Although trait formation only produces single-method traits, in requirements (which may not yet be fulfilled) will disappear from general traits will acquire several methods via composition. Each the trait as well, which is unsound. Our strategy is to remove m0 provided method has its own inlining assumptions, but we also from the trait’s type, but transitively record its requirements in the want to consider the inlining assumptions for the trait as a whole. inlining assumptions of any other provided method that invokes m0. We introduce a function Inl that yields the trait inlining assump- In effect, we are doing a one-step path compression on the method tions for given a trait type. Trait inlining assumptions are formed call graph. The hide function achieves this result: using the ] operator, which ensures that repeated assumptions ( 0 0 about a method or field will have the same type: 0 (ρ ] ρm0 ) \ m m ∈ ρ hide(ρ, m , ρm0 ) = U ρ otherwise ρ = m∈M(ρm ] hhm : τmii)Γ ` ρ ok m∈M Inl(h| m : τm@ρm |i) = ρ The type judgement for hiding simply applies hide to each pro- vided method, dropping m0: We also have a well-formedness check for trait types, which simply m∈M 0 checks that Inl can succeed in producing inlining assumptions for Γ ` T : h| m : τm@ρm |i m ∈ M 0 m∈M\m0 the trait: θ = h| m : τm@hide(ρm, m , ρm0 ) |i Γ ` θ ok Inl(θ) is defined Γ ` T hide m0 : θ Γ ` θ ok Notice that, if m0 is not used by any other provided method in the The well-formedness check for trait types does a lot of work for the trait, its requirements are dropped altogether. In this case, m0 is type system by centralizing coherency checking. Typing judgments dead code for the trait, and the method itself is eliminated in the need only include additional, specialized constraints. For trait com- dynamic semantics. position, the additional constraint is that the given traits are disjoint Finally, we have subclass formation. We first type the construc- (i.e., specify a disjointly-named set of provided methods): tor, trait, and superclass. We then ensure that the trait’s inlining assumptions about the class hold. A given type τ might be spec- Γ ` T1 : θ1 Γ ` T2 : θ2 l ified as part of the expected super-type, the expected self-type, dom(θ1) t dom(θ2)Γ ` θ1 ] θ2 ok and the actual superclass type; the typing judgement will only suc- Γ ` T1 + T2 : θ1 ] θ2 ceed if these specifications agree on the form of τl. Because of this Method exclusion requires that the method to be excluded is requirement, we need only check the relationships between the var- actually provided by the trait: ious label sets to ensure that the class is well-typed: f∈F Γ ` T : θ m ∈ θ Γ ` θ \ m ok Γ, x : τ ` econs : τcons Γ, x : τ ` eF : {f : τf } l∈LC Γ ` T exclude m : θ \ m Γ ` T : θ Γ ` C : τcons → {| l : τl |} F t LC L = LC ∪ F ∪ dom(θ) Method aliasing is somewhat more complex. For aliasing m as l∈Lsuper 0 hl : τl i = super(Inl(θ)) Lsuper ⊂ LC m , the typing judgment first ensures that m is a provided method l∈L hl : τ self i = self(Inl(θ)) L ⊂ L and that m0 is not. It then forms a new trait type by joining inlining l self assumptions for m0 to the old trait type. The inlining assumptions Γ ` λ(x : τ).(super econs) ⊕ eF in l∈L for m0 are the same as those for m; in particular, if m invoked itself, T extends C : τ → {| l : τl |} m0 will invoke m:4 Γ ` T : θ m ∈ θ m0 ∈/ θ 6. Dynamic semantics θ0 = θ ] h| m0 : θ(m) |i Γ ` θ0 ok We define evaluation with a big-step operational semantics. Evalu- Γ ` T alias m as m0 : θ0 ation judgments are written in the context of an environment E and a store S. Environments map names to trait, class, and expression Note that m0 may be the name of a required method, so aliasing can values. Stores map addresses to object values, allowing objects to be used to fulfill trait requirements. The same is true for renaming. have mutable fields. Declaration evaluation yields a new environ- Method renaming allows provided methods, required self- ment and possibly a new store, while expression evaluation (which methods, and required super-methods to be renamed. To rename can occur in a declaration evaluation) yields an expression value r to r0, the typing judgment checks that r and r0 are not field and possibly a new store: names, that r is mentioned in the inlining assumptions (i.e., it is 0 0 0 Program evaluation E,S ` P −→ ev, E ,S either provided or required), and that r is not a provided method. 0 0 0 Declaration evaluation E,S ` D −→ E ,S The new trait type is formed in two steps: first, r is renamed to r 0 in all of the sets of inlining assumptions; then, r is renamed to r0 Expression evaluation E,S ` e −→ ev, S in the resulting trait type, which will only have an effect if r is a At the core of our calculus is a revised notion of trait values. The standard view of trait values as simple collections of named 4 The issue of aliasing a recursive method is detailed in the dynamic seman- provided methods is insufficient to support trait privacy, and makes tics. a realistic model of deep renaming difficult to achieve. We adopt Riecke and Stone’s approach [RS02] and distinguish between in- We define ternal method names (which we term slots) and external method µfoo =def foo (x : unit): unit { self.bar() } names. In our model, trait values are collections of internally named provided methods, some of which may be externally named as well. µbar =def bar (x : unit): unit {···} To support deep renaming of required methods, we distinguish be- and derive tween internal and external names for them as well; the internal ∅, ∅ ` TFoo = h| µ ; hhbar : unit → unitii |i −→ E , ∅ name of a required method is eventually assigned to the method foo 1 E1, ∅ ` TBar = h| µbar; hh ii |i −→ E2, ∅ that fulfills that requirement. E , ∅ ` TFooBar = TFoo + TBar −→ E , ∅ More formally, a dictionary φ is a finite partial function that 2 3 maps method names to slots. A method suite value Mv maps slots where E3 = to method values. A trait value tv is a method suite value together 8 TFoo 7→ h| {1 7→ µvfoo}; {foo 7→ 1}; {bar 7→ 2} |i, 9 with dictionaries for its provided and required methods: > > < TBar 7→ h| {1 7→ µvbar}; {bar 7→ 1}; ∅ |i = ˛ ˛ r∈R fi {1 7→ µv , 2 7→ µv }; fl φ ::= {r 7→ i } dictionary > TFooBar 7→ ˛ foo bar ˛ > i∈I : ˛ {foo 7→ 1, bar 7→ 2}; ∅ ˛ ; Mv ::= {i 7→ µvi } method suite value ˛ ˛ µv ::= [E; φµ; λ(x : τ).e; ρ] method value so that tv ::= h| Mv; φP ; φR |i trait value E3 ` TFooBar alias foo as fiz fi˛ ˛fl To prove type soundness we will need to give types to trait values, ˛ {1 7→ µvfoo, 2 7→ µvbar, 3 7→ µvfoo}; ˛ −→ ˛ ˛ so we track inlining assumptions in method values. ˛ {foo 7→ 1, bar 7→ 2, fiz 7→ 3}; ∅ ˛ Method hiding and renaming only affect a trait value’s dic- tionaries, i.e., its external naming; its method suite remains un- E3 ` TFooBar rename bar to baz changed.5 Notice that a method value µv contains a dictionary φ fi˛ 0 0 ˛fl µ ˛ {1 7→ µvfoo, 2 7→ µvbar}; ˛ −→ ˛ ˛ in addition to the standard closure over the lexical environment ˛ {foo 7→ 1, baz 7→ 2}; ∅ ˛ E. This dictionary, established during evaluation of trait forma- tion, can be thought of as a closure over the trait’s current external E3 ` TFooBar exclude bar name environment. Unlike the trait itself, which has two dictionar- −→ h| {1 7→ µvfoo}; {foo 7→ 1}; {bar 7→ 2} |i ies, each method closure contains only the single dictionary φµ; = E3(TFoo) from the perspective of a particular method, there is no difference between provided and required methods, because by the time the E3 ` TFooBar hide bar method is invoked all required methods must have been provided. 00 00 −→ h| {1 7→ µvfoo, 2 7→ µvbar}; {foo 7→ 1}; ∅ |i It is φµ that allows a method to remain coherent in the presence of method hiding and renaming. E3 ` (TFooBar hide bar) + TBar Ultimately, the methods in a trait value will be inlined into a fi˛ {1 7→ µv00 , 2 7→ µv00 , 3 7→ µv }; ˛fl −→ ˛ foo bar bar ˛ class value. Method dispatch is dictionary-based, but the dictionary ˛ {foo 7→ 1, bar 7→ 3}; ∅ ˛ used is dependent on the location of the call (this is the crux of ˛ ˛ Riecke and Stone’s approach). More concretely: suppose we have E ` TFooBar hide foo a trait, TFooBar, which provides methods foo and bar. Further, 3 −→ h| {2 7→ µv }; {bar 7→ 2}; ∅ |i suppose that foo invokes bar. When the trait is first formed, we bar ≈ E (TBar) assign slots to foo and bar and record these assignments in the 3 φµ dictionaries for both methods. We can assume φµ = {foo 7→ 1, bar 7→ 2} for both methods.6 If we then rename bar to Figure 6. Evaluation examples baz, the dictionary for the trait itself is changed to reflect this foo baz foo (φp = { 7→ 1, 7→ 2}), but the dictionaries for and uation appear in the appendix. The complete formal system can be bar TFooBar remain the same. Suppose we inline into a class and found in an extended version of this paper [RT06]. instantiate an object obj. We are able to invoke obj.baz using a dictionary giving an external view of the object (similar to φP ). 6.1 Trait evaluation But if we invoke obj.foo, what happens when we reach the call E ` T −→ tv to self.bar, which no longer exists from the external viewpoint? Trait evaluation judgments have the form , since trait evaluation does not use or modify the store. We will often write The φµ dictionary associated with foo is used to discover the slot I M R for bar, which will be the same as the slot for baz. E ` T −→ h| Mv ; φP ; φR |i The previous example glosses over a few evaluation details, which are explained below. The main idea to keep in mind is the to assert that T evaluates to h| Mv; φP ; φR |i with dom(Mv) = I, motivation for all the dictionary juggling: we need to know what dom(φP ) = M, and dom(φR) = R; we always have that actual method to invoke, given a method name and context, and we M t R. For slot manipulation, we will use a function NS (“new need to do this in the face of aliasing, renaming, hiding, and ex- slots”) which takes a set of method names and a set of slots, and a cluding. Figure 6 shows the previous example and others in more function FS (“fresh slots”) which takes two sets of slots. NS yields formal detail; it should be read in parallel with the evaluation rules. a dictionary mapping each of the given names to a unique, new The remainder of this section gives a complete description of eval- slot not in the given set of slots. FS yields a translation function ϕ uation for traits and classes and describes the nonstandard portions which maps each of the slots in its first parameter to a unique slot of expression evaluation. Some additional rules for expression eval- not contained in its second parameter. In other words, φ = NS(M, I) 5 One of the rules for renaming changes the closures in the suite, but it leaves dom(φ) = M rng(φ) t I φ is one-to-one the slot assignments of the suite intact. 6 + ϕ = FS(I , I ) In this example and others, we let IU = Z , but formally IU is held 1 2 abstract. dom(ϕ) = I1 rng(ϕ) t I2 ϕ is one-to-one For example, we might have inlining assumptions for each provided method: E ` T −→ h| I Mv ; M φ ; R φ |i NS({m , m }, {1, 2, 3}) = {m 7→ 4, m 7→ 5} P R 1 2 1 2 r ∈ M r0 ∈/ M r0 ∈/ R FS({1, 4}, {1, 2, 3}) = {1 7→ 4, 4 7→ 5} i∈I Mv = {i 7→ [Ei; φi; ei; ρi] } 0 0 φP = (φP \ r)[r 7→ φP (r)] 0 0 i∈I Evaluating a trait formation expression to a trait value estab- Mv = {i 7→ [Ei; φi; ei; ρi[r /r]] } lishes the initial slot assignment for the provided method and each E ` T rename r to r0 −→ h| Mv 0; φ0 ; φ |i required (super- or self-) method using NS. Slots are not estab- P R lished for field requirements, which cannot be renamed in our The rule for renaming required methods is similar, but works on model. The resulting dictionary is used as the external name clo- φR: sure in the constructed method values: I M R E ` T −→ h| Mv ; φP ; φR |i r ∈ R r0 ∈/ M r0 ∈/ R φP = NS({m}, ∅) φR = NS(dom(ρ) \FU , {φP (m)}) i∈I Mv = {φP (m) 7→ [E; φP ∪ φR; λ(x : τ).e; ρ] } Mv = {i 7→ [Ei; φi; ei; ρi] } 0 0 φR = (φR \ r)[r 7→ φR(r)] E ` h| m (x : τ1): τ2 {e}; ρ |i −→ h| Mv; φP ; φR |i 0 0 i∈I Mv = {i 7→ [Ei; φi; ei; ρi[r /r]] } 0 0 0 Notice that the dictionaries φP and φR are joined as a single dictio- E ` T rename r to r −→ h| Mv ; φP ; φR |i nary in the method value. An alternative presentation of trait values Renaming a provided method to fulfill a method requirement is could maintain a single dictionary at the trait level, and determine more complex. Unlike the situation for aliasing, we are not estab- which methods were actually provided by examining dom(Mv). lishing a new, independent method, and thus the slot assignment For clarity and simplicity, we separate the dictionaries and maintain for the original method must be retained. At the same time, the the invariants rng(φP ) ⊆ dom(Mv) and dom(Mv) t rng(φR). required method being fulfilled has its own slot assignment which The set of slots used by a trait is dom(Mv) ∪ rng(φR). must also be retained. Both of these assignments represent commit- Aliasing a method does not simply map a new external name to 0 ments made to the φµ dictionaries in the method closures for the the existing internal name for the method; if we alias m as m , we trait. To fulfill the commitments, we slightly modify the promise want the two methods to share implementations but to otherwise be by altering the target slots for φµ; we arbitrarily choose to drop independent, so that in particular we may later exclude m without 0 the required method’s slot in favor of the provided method’s slot. impacting m . Another concern is recursion: if m invokes itself, The modification is performed by composing a translation function and we alias m as m0, what should happen to the recursive call ϕ with the original φµ dictionaries. The translation is the identity for m0? To see why this is important, consider that after aliasing 0 function on slots except at the required method’s slot, where it maps m as m , we could exclude m from the trait and replace it with to the provided method’s slot: some other method. This is a somewhat thorny issue, because if we 0 I M R choose to have m recurse on itself rather than invoking m, we have E ` T −→ h| Mv ; φP ; φR |i only dealt with direct recursion; if m recurses on itself indirectly r ∈ M r0 ∈/ M r0 ∈ R 0 i∈I via some other method, m would still end up invoking m via that Mv = {i 7→ [Ei; φi; ei; ρi] } 0 0 same method. To keep the semantics simple and consistent, aliasing φP = (φP \ r)[r 7→ φP (r)] 0 0 m as m will leave invocations of m as still calling m. ϕ = {i 7→ i, φR(r ) 7→ φP (r)} Aliasing has two cases, which we handle with different rules. 0 0 i∈I Mv = {i 7→ [Ei; ϕ ◦ φi; ei; ρi[r /r]] } A method can be aliased to fulfill a method requirement, in which E ` T rename r to r0 −→ h| Mv 0; φ0 ; φ \ m0 |i case the slot assigned for the required method is used for the alias, P R and the requirement is removed from φR: To support hiding and excluding methods, and to properly type trait values, we need to drop unreachable hidden methods and un- I M R 0 E ` T −→ h| Mv ; φP ; φR |i used required methods. The auxiliary judgement form tv ,→ tv m ∈ M m0 ∈/ M m0 ∈ R rewrites a trait value after “dead-code elimination.” The judgement 0 0 0 φP = φP [m 7→ φR(m )] collects all of the slots mentioned in the inlining assumptions of 0 0 Mv = Mv[φR(m ) 7→ Mv(φP (m))] the trait’s provided methods, using the dictionary stored with each E ` T alias m as m0 −→ h| Mv 0; φ0 ; φ \ m0 |i method to convert from method names to slots. The method suite P R and required method dictionary are then restricted to include only −1 the mentioned slots; the notation φR (I) signifies the inverse im- If the aliasing operation does not fulfill a method requirement, we age of I under φ : use NS to establish a new internal name for the alias: R i∈dom(Mv) Mv = {i 7→ [Ei; φi; ei; ρi] }IP = rng(φP ) I M R E ` T −→ h| Mv ; φP ; φR |i “S ” −1 I = IP ∪ φi(dom(ρi)) R = φ (I) m ∈ M m0 ∈/ M m0 ∈/ R i∈IP R 0 0 φP = φP ∪ NS({m }, I ∪ rng(φR)) h| Mv; φP ; φR |i ,→ h| Mv ↓ I; φP ; φR ↓ R |i Mv 0 = Mv[φ0 (m0) 7→ Mv(φ (m))] P P Hiding a method removes it from the provided method dictio- 0 0 0 E ` T alias m as m −→ h| Mv ; φP ; φR |i nary. It also updates the inlining assumptions of all provided meth- ods, using the hide function from the static semantics:

As with aliasing, provided methods may be renamed to fulfill I M R required methods. In addition, required methods may be renamed. E ` T −→ h| Mv ; φP ; φR |i i∈I We give three rules for renaming. To rename a provided method m ∈ M j = φP (m) Mv = {i 7→ [Ei; φi; ei; ρi] } 0 i∈I without fulfilling a method requirement, we remove its old external Mv = {i 7→ [Ei; φi; ei; hide(ρi, m, ρj )] } 0 name from the φP dictionary and insert a new mapping from the h| Mv ; φP \ m; φR |i ,→ tv new name to the existing slot. We also rename the method in the E ` T hide m −→ tv A method m hidden by this operation may still be available in the Note that this rewriting does not create any slots; since super- method suite, via its internal name: previously established methods methods are treated as requirements, we simply use the slots from can gain access to m by looking up the slot for m in the φµ the required method dictionary. dictionary bundled with their closure. If m is not used elsewhere Evaluation of inheritance is similar to evaluation of trait compo- in the trait, however, it is dropped. sition: we are reconciling two method suites with incompatible slot To exclude a method m from a trait, it must be removed from assignments. In this case, however, a method with the same exter- both φP and Mv. Because other methods in the trait may refer- nal name may be provided by both the trait and the superclass. We ence m, we add m to φR, maintaining the internal name for m; retain the class’s slot assignment for the method, but use the trait’s dead-code elimination will remove this spurious requirement, and implementation, thereby overriding the method: m possibly others, if is not actually needed: I M R E ` T −→ tv tv = h| Mv ; φP ; φR |i I M R E ` T −→ h| Mv ; φP ; φR |i m ∈ M E ` C −→ cv cv = {| λv super; Mv C ; φC |} 0 h| Mv \ φP (m); φP \ m; φR[m 7→ φP (m)] |i ,→ tv i∈I cv ` tv =⇒ {i 7→ [Ei; φi; ei; ρi] } m∈M∩dom(φC ) E ` T exclude m −→ tv ϕP = {φP (m) 7→ φC (m) } m∈R∩dom(φC ) The most complex trait operation is composition. The two com- ϕR = {φR(m) 7→ φC (m) } ` 0 ´ posed traits must have disjoint external names for their provided ϕF = FS I \ dom(ϕP ), dom(Mv C ) methods, but there may be considerable overlap in their internal ϕ = ϕP ∪ ϕR ∪ ϕF 0 i∈I0 naming, so we must adjust slot assignments accordingly. We use Mv T = {ϕ(i) 7→ [Ei; ϕ ◦ φi; ei; ρi] } 0 0 a technique similar to the one described for renaming, and treat Mv C = (Mv C \ rng(ϕP )) ∪ Mv T the slot assignments of one of the traits as authoritative, creating a super ∈/ dom(E) Econs = E[super 7→ λv super] translation ϕ to adjust the other trait’s dictionaries: λv cons = [Econ ; λ(x : τ).(super econs ) ⊕ eF ]

I1 M1 R1 E ` λ(x : τ).(super econs) ⊕ eF in E ` T1 −→ h| Mv 1; φP1 ; φR1 |i 0 I2 M2 R2 T extends C −→ {| λv cons ; Mv C ; φC ∪ (ϕ ◦ φP ) |} E ` T2 −→ h| Mv 2; φP2 ; φR2 |i M M Mv = {i 7→ [E ; φ ; e ; ρ ] i∈I2 } 1 t 2 2 i i i i In reading the above rule, recall that rng(φR) t I. We also have ϕ = {φ (m) 7→ φ (m) m∈M2∩R1 } 0 P P2 R1 that dom(ϕR) t I , because no super-method name (super.m) m∈R2∩M1 ϕR = {φR2 (m) 7→ φP1 (m) } can appear in φC . 0 0 I1 = I1 ∪ rng(φR1 ) I2 = I2 ∪ rng(φR2 ) 0 0 ϕF = FS(I2 \ dom(ϕR ∪ ϕP ), I1) 6.3 Object instantiation and method dispatch ϕ = ϕP ∪ ϕR ∪ ϕF φP = φP ∪ (ϕ ◦ φP ) 1 2 Expression evaluation is written E,S ` e −→ ev,S0. Most of φR = (φR1 ∪ (ϕ ◦ φR2 )) \ (M1 ∪ M2) Mv = Mv ∪ {ϕ(i) 7→ [E ; ϕ ◦ φ ; e ; ρ ] i∈I2 } the rules for expression evaluation are standard, but we describe the 1 i i i i rules related to object instantiation and method dispatch to high- E ` T + T −→ h| Mv; φ ; φ |i 1 2 P R light the role that φµ dictionaries play. The remaining expression The construction of the translation ϕ is performed in three steps: evaluation rules can be found in Appendix B. we construct ϕP for the methods provided in T2 that fulfill meth- An object value is a field record value paired with a method ods required by T1; we construct ϕR for the methods required in suite: T2 that are provided by T1; finally, we construct ϕF to map all ov ::= hfv; Mvi object value remaining slot assignments from T2 to fresh slots that do not oc- f∈F fv ::= {f = ev f } field record value cur in T1. A new method suite Mv joins the method suite from T1 ev ::= (a, φ) object reference and an adjusted method suite for T2 that reflects the translated slot | λv function value assignments. | fv field record value | () unit value 6.2 Class evaluation Class evaluation results in an evaluated constructor, a method suite, Evaluating an object instantiation takes a class and a constructor a and a dictionary into that method suite: parameter and updates the store to map a new address to a new object value. The evaluation yields the address a paired with the cv ::= {| λv; Mv; φC |} class value class’s dictionary φC : λv ::= [E; λ(x : τ).e] function value E ` C −→ {| [EF ; λx.eF ]; Mv; φC |} The judgment form for class evaluation is written E ` C −→ E,S ` e −→ ev,S1 cv; as with traits, the store is not used when evaluating a class EF [x 7→ ev],S1 ` eF −→ fv,S2 expression. We evaluate nil to the empty class value, writing {} a∈ / dom(S2) for the empty field record: E,S ` new C e −→ (a, φC ),S2[a 7→ hfv; Mvi]

E ` nil −→ {| [∅; λx.{}]; ∅; ∅ |} To evaluate a method dispatch e.m, we first evaluate e to an address a and dictionary φ. We look up the object value associated Handling inheritance requires us to deal with super-invocations. with the address, then use the dictionary to index into the object’s Since class methods may be overridden, and hence no longer ac- method suite at m, yielding the closure for the method m. The cessible from the class’s method suite, we bind super-invocations evaluation results in a function value equivalent to m’s closure: the to new, hidden provided methods. The judgment form cv ` tv =⇒ environment of the function value takes self to (a, φµ) so that self- Mv extends the method suite in tv to a new method suite that pro- invocations within m have m’s view of the class: vides the relevant super-methods from cv: E,S ` e −→ (a, φ),S0 S0(a) = hfv; Mvi 0 s∈dom(φR)∩SU Mv = Mv ∪ {φR(s) 7→ Mv C (φC (s)) } Mv(φ(m)) = [Em; φm; λ(x : τ).em; ρm] 0 0 {| λv super; Mv C ; φC |} ` h| Mv; φP ; φR |i =⇒ Mv E,S ` e.m −→ [Em[self 7→ (a, φm)]; λ(x : τ).em],S To support super-method invocation, we invoke the super- environment E, a type τ 0, and an expression value ev such that method name in the context of self: Σ ` E :Γ and ` S :Σ and ∅, ∅ ` P −→ ev,E,S and 0 0 E,S ` self.(super.m) −→ ev,S0 Σ ` ev : τ and  ` τ <: τ. 0 E,S ` super.m −→ ev,S This result says that a well-typed, terminating program P does This approach works because super-method names are indexed in not go wrong. In particular, P will evaluate to a result that improves the dictionary for each method, and super-method implementations on its static type. While this is a weaker statement than soundness are provided, with the appropriate slot, during class formation. for a small-step semantics, a characterization of terminating pro- Notice that this rule does not apply to the constructor for a class, grams is sufficient for our purposes with this calculus. which does not invoke a super-method but invokes “super” itself. Finally, the evaluation of self is a simple lookup in the current 8. Related work environment. Traits were originally proposed as a mechanism for SMALLTALK by Scharli¨ et al. [SDNB03]. In addition to studying the language E,S ` self −→ E(self ),S design and methodological issues, they also developed a formal model for their system [SDN+02]. The most important difference 7. Type soundness between our system and this original work is that we are working in A detailed proof of type soundness and the run-time typing rules a strongly-typed setting, instead of an untyped language. Another for the system can be found in the tech report version of this major point of difference is that we have abandoned the flatten- paper [RT06]. Here we give a brief overview of the issues and ing property [NDS06] in favor of supporting deep renaming and theorems related to type soundness. private methods in traits.7 In this sense our system is not a con- One important issue for type soundness is typing for link-time servative extension of traditional class-based designs, but the meta- and run-time values. We must give types to trait values, class val- programming techniques enabled by our system provide an argu- ues, and expression values, including object addresses. However, ment for a dictionary-based semantics. Further argument for this because stores may be cyclic, we cannot type addresses via a recur- approach can be found in Riecke and Stone’s paper [RS02]. sive examination. We follow the standard technique and introduce The introduction of traits for SMALLTALK has prompted a flurry a store typing Σ, which is a map from object addresses to object of work on traits for statically-typed languages. Fisher and Reppy types. The intuition behind this approach is that well-typed pro- developed the first formal model of traits in a statically typed set- grams will always store values of the same type in a given address, ting [FR04]. This model was subsequently extended to support so we need only type locations when they are first introduced. Run- polymorphic traits (key for examples such as the synchronized time typing judgments are given in terms of store typings, and thus readers) and stateful objects [FR03]. This extended trait calculus have the form Σ `  : . Since environments and stores are also was the starting point for the system in this paper. We have made a runtime values, we type them as well: the judgment Σ ` E :Γ number of refinements in the static semantics. Our calculus tracks says that environment E has type Γ, while ` S :Σ says that store method requirements on a per-method basis, which provides more S has type Σ. accuracy when excluding methods. We have also unified the han- Type preservation is easy to state for a big-step semantics, dling of methods and super methods in the requirements by intro- but the difficulty of stating a progress property is usually held as ducing a separate namespace for super methods (e.g. super.m). a drawback of the big-step style. The difficulty with progress is While a simple trick, this technique streamlines the static semantics that, for a big-step semantics, non-termination and WRONG are significantly. We have reformulated the link-time and dynamic se- indistinguishable. In particular, if we do not have ∅, ∅ ` P −→ mantics of method binding using Riecke-Stone dictionaries, which ev,E,S, it could either be that P diverges or that P goes wrong. allows the support for the deep operations of renaming and hiding To overcome this problem, we follow [FR04] and introduce a at the trait level. These last two are our most significant additions height function HE,S , which gives the height of the derivation tree to the Fisher-Reppy calculus. for a program under E and S. We have the following definition: Smith and Drossopoulou recently described a family of three different extensions of JAVA with traits [SD05]. The first of these, DEFINITION 7.1 (Divergence). We say a program P diverges if Chai 1, defers all checking until traits have been included in a class. there is no n such that H∅, ∅(P ) = n. The second, Chai 2, adds trait types and is similar in expressiveness to the Fisher-Reppy trait calculus, with a couple of exceptions. Like If E,S ` P −→ ev then HE,S (P ) = n, where n is the height JAVA, Chai uses nominal subtyping, instead of structural typing, of the derivation tree for the judgment. If P diverges in the context 2 and does not have polymorphic traits. The differences between our of E and S, then H (P ) diverges as well. Most importantly, E,S work the Fisher-Reppy calculus apply to Chai as well. Chai if P does not evaluate to any ev under E and S, then H (P ) 2 3 E,S extends Chai by allowing traits to be replaced at runtime. This converges, and measures the height of the evaluation derivation up 2 feature is orthogonal to the focus of our work, but could be added to the point that P went wrong. For example, to our system. HE,S (t = (~α)T ; P ) = Another proposed design of traits for JAVA is Featherweight Trait JAVA (FTJ) [LS04], which adds traits to Featherweight ( 0 H 0 (P ) if E,S ` t = (~α)T −→ E ,S JAVA [IPW01]. This system is fairly similar to the Fisher-Reppy 1 + E ,S 1 otherwise trait calculus (with some technical differences), and does not sup- port either deep renaming or private methods at the trait level. Thus, the height function allows us to distinguish between non- There are strong similarities between traits and mixins [BC90, + termination and stuck states, without having explicit WRONG tran- FKF98, OAC 04], which are another mechanism designed to give sitions. Using this height function, we can state and prove the fol- many of the benefits of multiple inheritance without the complica- lowing soundness result [RT06]: 7 Strictly speaking, one has to reformulate the flattening property for our THEOREM 7.1 (Type soundness). If ∅ `P P : τ then either P system, since we do not have a mechanism for defining methods directly in diverges or there exist a store typing Σ, a store S, a context Γ, an a class. tions. The main difference between mixins and traits is that mixins • The most important questions posed by our work regard the force a linear order in their composition (it is this order that avoids design of a trait metalanguage. At the least, such a language the complexities of the diamond property). This linear order intro- should include an explicit abstraction mechanism for trait duces fragility problems and may make code maintenance more method names, but the design space is large and essentially difficult [SDNB03]. Mixin mechanisms must also deal with con- unexplored. We plan to first implement the trait calculus of this structor functions, which can be another source of fragility, since it paper in the language MOBY, which will allow us to gain expe- is hard to predict what the interface of the super-class construc- rience with trait programming and manual trait-based metapro- tor will be. A solution to this problem is to define the mixin’s gramming. We hope that programming experience will lead to constructors at the point of mixin application [ALZ03]. This is- the recognition of patterns and idioms that can then be codified sue does not affect traits, since they are not defining or initializ- into a trait metalanguage. ing object state. Personalities are another trait-like mechanism de- Acknowledgments We would like to thank Kathleen Fisher, who signed for JAVA, although they are much more limited in their ex- provided helpful feedback on an early draft and insight into the type pressiveness [Bla98] and they do not have a formal model. The theory for the paper. The anonymous reviewers gave thorough feed- language Scala has a special form of abstract class called a trait back on the formal system and good insights on the presentation; class [OAC+04]. Trait classes are used as mixins and also to sup- their work is appreciated. port family polymorphism, but they do not support the trait opera- tions such as exclusion, or our deep renaming. Bracha’s Jigsaw framework is often cited as the first formal ac- References count of mixins [Bra92]. Like our calculus, and unlike the other [ALZ03] Ancona, D., G. Lagorio, and E. Zucca. Jam—designing a java trait systems discussed above, Jigsaw supports deep renaming extension with mixins. ACM Transactions on Programming (Bracha calls it global renaming) and method hiding. His system Languages and Systems, 25(5), September 2003, pp. 641–712. also has a static binding mechanism (called freezing). Bracha gave [BC90] Bracha, G. and W. Cook. Mixin-based inheritance. In a dynamic semantics and a type system for Jigsaw, but did not ECOOP’90, New York, NY, October 1990. ACM, pp. 303– prove type soundness. While it is possible that our metaprogram- 311. ming idiom could be applied to mixins in the Jigsaw framework, [Bla98] Blando, L. Designing and programming with personalities. we believe that traits are a better fit. Master’s dissertation, Northeastern University, Boston, MA, Examples like the TSync trait closely resemble the use of as- December 1998. Available as Technical Report NU-CCS-98- pects [KLM+97] to specify “cross-cutting” concerns. While traits 12. have always had some overlap with aspects, trait-based metapro- [Bra92] Bracha, G. The Jigsaw: Mixins, gramming brings the two even closer. Both traits and aspects are Modularity and Multiple Inheritance. Ph.D. dissertation, specified outside of the classes to which they are applied; the pri- University of Utah, March 1992. mary difference between the two is how they are applied to classes. [BSD03] Black, A. P., N. Scharli,¨ and S. Ducasse. Applying traits to the Aspects specify points of application via pointcuts, which pick out Smalltalk collection classes. In OOPSLA’03, New York, NY, join points in the target classes; hence, aspects are in control of October 2003. ACM, pp. 47–64. their own application. On the other hand, traits are completely in- [FKF98] Flatt, M., S. Krishnamurthi, and M. Felleisen. Classes and ert unless they are explicitly inlined during class formation, leaving mixins. In POPL’98, New York, NY, January 1998. ACM, pp. control to the class implementor. 171–183. [FR99] Fisher, K. and J. Reppy. The design of a class mechanism for Moby. In PLDI’99, New York, NY, May 1999. ACM, pp. 9. Conclusion 37–49. [FR03] Fisher, K. and J. Reppy. Statically typed traits. Technical Traits provide a promising mechanism for constructing class hierar- Report TR-2003-13, Dept. of Computer Science, U. of chies from reusable components. We have introduced two new trait Chicago, Chicago, IL, December 2003. operations, method hiding and renaming, which are the deep coun- [FR04] Fisher, K. and J. Reppy. A typed calculus of traits. In FOOL11, terparts to method exclusion and aliasing. These operations provide January 2004. new ways of resolving conflicts in trait composition. Furthermore, they support privacy at the trait level and trait-based metaprogram- [IPW01] Igarashi, A., B. C. Pierce, and P. Wadler. Featherweight java: ming. Our formal model gives a detailed semantics to these oper- a minimal core calculus for java and gj. ACM Transactions on Programming Languages and Systems, 23(3), 2001, pp. ations in a statically typed setting, while also improving the gran- 396–450. ularity of the type system over previous calculi. There are several + remaining questions for a concrete language design built around [KLM 97] Kiczales, G., J. Lamping, A. Menhdhekar, C. Maeda, our calculus, which we briefly raise: C. Lopes, J.-M. Loingtier, and J. Irwin. Aspect-oriented pro- gramming. In M. Aks¸it and S. Matsuoka (eds.), ECOOP’97, vol. 1241 of LNCS, pp. 220–242. Springer-Verlag, New York, • In a language with a rich module system, it is not yet clear NY, 1997. how traits should interact with features such as signatures and functors. One can imagine using signature ascription to imple- [LS04] Liquori, L. and A. Spiwack. Featherweight-trait java: A trait- based extension for FJ. Technical Report RR-5247, Institut ment method hiding in traits (as done in MOBY at the class National de Recherche en Informatique et en Automatique, level [FR99]). June 2004. • While the type system we have presented in this paper is flex- [NDS06] Nierstrasz, O., S. Ducasse, and N. Scharli.¨ Flattening traits. ible, it is also verbose, since each method provided by a trait Journal of Object Technology, 5(3), May 2006. (to appear). specifies its own requirements. This granular type information [OAC+04] Odersky, M., P. Altherr, V. Cremet, B. Emir, S. Micheloud, can be inferred from a trait definition, but introducing traits into N. Mihaylov, M. Schinz, E. Stenman, and M. Zenger. The a module system will require a programmer to explicitly write Scala Language Specification (Draft). Programming Methods down trait signatures, and it is not clear what form such signa- Laboratory, EPFL, Switzerland, February 2004. Available tures should take. from lamp.epfl.ch/scala. [Qui04] Quitslund, P. J. Java traits — improving opportunities for x∈ / dom(Γ) Γ ` e : τ reuse. Technical Report CSE 04-005, OGI School of Science Γ ` x = e ⇒ Γ, x : τ & Engineering, September 2004.

[RS02] Riecke, J. G. and C. A. Stone. Privacy via subsumption. Program typing: Γ `P P : τ Information and Computation, 172(1), January 2002, pp. 2– 0 0 28. A preliminary version appeared in FOOL5. Γ ` D ⇒ Γ Γ `P P : τ Γ ` e : τ

[RT06] Reppy, J. and A. Turon. A foundation for trait-based Γ `P D; P : τ Γ `P e : τ metaprogramming (extended version). Technical report, Dept. of Computer Science, U. of Chicago, Chicago, IL, 2006. B. Additional evaluation rules [SD05] Smith, C. and S. Drossopoulou. Chai: Traits for Java-like languages. In ECOOP’05, LNCS, New York, NY, July 2005. Program evaluation: E,S ` P −→ ev,E0,S0 Springer-Verlag. [SDN+02] Scharli,¨ N., S. Ducasse, O. Nierstrasz, R. Wuyts, and A. Black. E,S ` D −→ E0,S0 Traits: The formal model. Technical Report CSE 02-013, OGI E0,S0 ` P −→ ev,E00,S00 E,S ` e −→ ev,S0 School of Science & Engineering, November 2002. (revised 00 00 0 February 2003). E,S ` D; P −→ ev,E ,S E,S ` e −→ ev,E,S [SDNB03] Scharli,¨ N., S. Ducasse, O. Nierstrasz, and A. Black. Traits: Declaration evaluation: E,S ` D −→ E0,S0 Composable units of behavior. In ECOOP’03, vol. 2743 of LNCS, New York, NY, July 2003. Springer-Verlag, pp. 248– 274. E ` T −→ tv t∈ / dom(E) [Str94] Stroustrup, B. The Design and Evolution of C++. Addison- E,S ` t = (~α)T −→ E[t 7→ (~α)tv],S Wesley, Reading, MA, 1994. E ` C −→ cv c∈ / dom(E) A. Additional typing judgments E,S ` c = C −→ E[c 7→ cv],S

Subtyping: Γ ` τ1 <: τ2 E,S ` e −→ ev x∈ / dom(E) E,S ` x = e −→ E[x 7→ ev],S0 l∈L1 Γ ` τ ok Γ ` hl : τl i ok L2 ⊂ L1 l∈L1 l∈L2 0 Γ ` τ <: τ Γ ` hl : τl i <: hl : τl i Expression evaluation: E,S ` e −→ ev,S

0 0 00 00 Γ ` τ2 <: τ1 Γ ` τ1 <: τ2 x ∈ E 0 00 0 00 Γ ` τ1 → τ1 <: τ2 → τ2 E,S ` x −→ E(x),S E,S ` λ(x : τ).e −→ [E; λ(x : τ).e],S Expression typing: Γ ` e : τ 0 E,S ` e1 −→ [E ; λ(x : τ).e],S1 Γ ` ok x ∈ dom(Γ) Γ, x : τ1 ` e : τ2 E,S1 ` e2 −→ ev 2,S2 Γ ` x : Γ(x) Γ ` λ(x : τ).e : τ1 → τ2 0 E [x 7→ ev 2],S2 ` e −→ ev,S3

0 l∈L E,S ` e1 e2 −→ ev,S3 Γ ` e1 : τ → τ Γ ` C : τ → {| l : τl |} 0 Γ ` e2 : τ Γ ` e : τ 0 0 E,S ` e −→ (a, φ),S S (a) = hfv; Mvi fv(f) = ev f l∈L Γ ` e1 e2 : τ Γ ` new C e : hl : τl i 0 E,S ` e.f −→ ev f ,S

Γ ` ok Γ ` Γ(super) <: hm : τmi E,S ` e1 −→ (a, φ),S1 E,S1 ` e2 −→ ev 2,S2 Γ ` self : Γ(self ) Γ ` super.m : τm f∈F S2(a) = h{f = ev f }; Mvi f∈F\f 0 0 S3 = S2[a 7→ h{f = ev f , f = ev 2}; Mvi] Γ ` e1 : τ Γ ` τ <: hf : τf i 0 Γ ` ok Γ ` e2 : τf E,S ` e1.f := e2 −→ (),S3 Γ ` () : unit Γ ` e1.f := e2 : unit f∈F1 E,S ` e1 −→ {f = ev f },S1 f∈F2 Γ ` ok Γ ` ef : τf ∀f ∈ F E,S1 ` e2 −→ {f = ev f },S2 f∈F f∈F f∈F1∪F2 Γ ` {f = ef } : {f : τf } E,S ` e1 ⊕ e2 −→ {f = ev f },S2

f∈F1 Γ ` e1 : {f = ef } E,S ` e1 −→ ev 1,S1 f∈F2 F1 t F2 Γ ` e2 : {f = ef } . . f∈F1∪F2 Γ ` e1 ⊕ e2 : {f = e } f E,Sn−1 ` en −→ ev n,Sn Γ ` e : τ E,S ` {f1 = e1, . . . , fn = en} −→ 0 Γ ` τ <: hl : τli Γ ` e : τ Γ ` τ <: τ {f1 = ev 1, . . . , fn = ev n},Sn 0 Γ ` e.l : τl Γ ` e : τ E,S ` {} −→ {},S E,S ` () −→ (),S Declaration typing: Γ ` D ⇒ Γ0

t∈ / dom(Γ) Γ, ~α ` T : θ c∈ / dom(Γ) Γ ` C : χ Γ ` t = (~α)T ⇒ Γ, t : Λ(~α).θ Γ ` c = C ⇒ Γ, c : χ