Multiple Inheritance Without Diamonds

Multiple Inheritance Without Diamonds

CZ: Multiple Inheritance Without Diamonds Donna Malayeri Carnegie Mellon University [email protected] fortunately however, problems arise when integrating modular mul- Abstract timethods even with restricted forms of multiple inheritance, such Multiple inheritance has long been plagued with the “diamond” in- as traits or Java multiple interface inheritance. Previous work either heritance problem, leading to solutions that restrict expressiveness, disallows multiple inheritance across module boundaries, or bur- such as mixins and traits. Instead, we address the diamond problem dens programmers by requiring that they always provide (possibly directly, considering two important difficulties it causes: ensuring a numerous) disambiguating methods. correct semantics for object initializers, and typechecking multiple To solve these problems, we take a different approach: while dispatch in a modular fashion. We show that previous solutions to permitting multiple inheritance, we disallow inheritance diamonds these problems are either unsatisfactory or cumbersome, and sug- entirely. So that there is no loss of expressiveness, we divide the gest a novel approach: supporting multiple inheritance but forbid- notion of inheritance into two concepts: an inheritance dependency ding diamond inheritance. Expressiveness is retained through two (expressed using a requires clause, an extension of a Scala con- features: a “requires” construct that provides a form of subtyping struct [32]) and actual inheritance. Through examples, we illustrate without inheritance (inspired by Scala [33]), and a dynamically- that programs that require diamond inheritance can be translated dispatched “super” call similar to that found in traits. Through ex- to a hierarchy that uses a combination of requires and multiple amples, we illustrate that inheritance diamonds can be eliminated inheritance, without the presence of diamonds. As a result, our lan- via a combination of “requires” and ordinary inheritance. We pro- guage, CZ—for cubic zirconia—retains the expressiveness of dia- vide a sound formal model for our language and demonstrate its mond inheritance. modularity and expressiveness. We argue that a hierarchy with multiple inheritance is conceptu- ally two or more separate hierarchies. These hierarchies represent 1. Introduction different “dimensions” of the class that is multiply inherited. We express dependencies between these dimensions using requires, Single inheritance, mixins [11, 21, 20], and traits [17, 33] each have and give an extended example of its use in Sect. 5. disadvantages: single inheritance restricts expressiveness, mixins Our solution has two advantages: fields and multiple inheritance must be linearly applied, and traits do not allow state. Multiple (including initializers) can gracefully co-exist, and multiple dis- inheritance solves these problems, but poses challenges itself. patch and multiple inheritance can be combined. To achieve the There are two well-known problems with multiple inheritance: latter, we make an incremental extension to existing techniques for (a) a class can inherit multiple features with the same name, and modular typechecking of multiple dispatch.1 (b) a class can have more than one path to a given ancestor (i.e., An additional feature of our language is a dynamically- the “diamond problem”, also known as “fork-join” inheritance) dispatched super call, modelled after trait super calls [17]. When [34, 36]. The first, the conflicting-features problem, can be solved a call is made to A.super. f () on an object with dynamic type by allowing renaming (e.g., Eiffel [27]) or by linearizing the class D, the call proceeds to f defined within D’s immediate superclass hierarchy [37, 36]. However, there is still no satisfactory solution along the A path (i.e., E where D extends E and E A). (The to the diamond problem. path must always be specified because D can have more than one The diamond problem arises when a class C inherits an ancestor parent.) With dynamically-dispatched super calls and requires, A through more than one path. This is particularly problematic our language attains the expressiveness of traits while still allow- when A has fields—should C inherit multiple copies of the fields ing classes to inherit state. or just one? Virtual inheritance in C++ is designed as one solution We have formalized our system as an extension of Feather- for C to inherit only one copy of A’s fields [19]. But with only one weight Java (FJ) [24] (Sect. 7) and have proved it sound [25]. copy of A’s fields, object initializers are a problem: if C transitively Contributions. calls A’s initializer, how can we ensure that it is called only once? • The design of a novel multiple inheritance scheme that solves Existing solutions either restrict the form of constructor definitions, (1) the object initialization problem and (2) the modular type- or ignore some constructor calls. checking of external methods, by forbidding diamond inheri- There is another consequence of the diamond problem: it causes tance (Sect. 6). multiple inheritance to interact poorly with modular typechecking • Generalization of the requires construct and integration with of multiple dispatch. Multiple dispatch is a very powerful language dynamically-dispatched super calls (Sect. 6). mechanism that provides direct support for extensibility and soft- • Examples that illustrate how a diamond inheritance scheme can ware evolution [13, 15]; for these reasons, it has been adopted by be converted to one without diamonds (Sections 4 and 5). designers of new programming languages, such as Fortress [2]. Un- • A formalization of the language (Sect. 7) and proof of type safety. Copyright is held by the author/owner(s). 1 FOOL ’09 24 January, Savannah, Georgia, USA. Without loss of generality, our formal system includes external methods ACM . (also known as open classes) rather than full multimethods; see Sect. 2. Stream « constructor » method Stream.seek { Stream(int) /∗∗ default implementation: do nothing ∗/ void Stream.seek(long pos) { } InputStream OutputStream /∗∗ seek if pos <= eofPos ∗/ « constructor » « constructor » void InputStream.seek(long pos) { ... } Stream.super(1024); InputStream(int) OutputStream(int) Stream.super(2048); /∗∗ if pos > eofPos, fill with zeros ∗/ void OutputStream.seek(long pos) { ... } InputOutputStream } In the context of our diamond hierarchy, this method definition Figure 1. An inheritance diamond. Italicized class names indicate is ambiguous—what if seek() is called on an object of type abstract classes. InputOutputStream? Unfortunately, it is difficult to perform a modular check to determine this fact. When typechecking the defi- nition of seek(), we cannot search for a potential subclass of both InputStream and OutputStream, as this analysis would not be 2. The Problem modular. And, when typechecking InputOutputStream, we can- not search for external methods defined on both of its superclasses, To start with, the diamond problem raises a question: should class as that check would not be modular, either. We provide a detailed C with a repeated ancestor A have two copies of A’s instance description of the conditions for modularity in Sect. 7.1. variables or just one—i.e., should inheritance be “tree inheritance” It is important to note that this problem is not confined to or “graph inheritance” [12]? As the former may be modelled using multiple (implementation) inheritance—it arises in any scenario composition, the latter is the desirable semantics; it is supported in where an object can have multiple dynamic types (or tags) on which languages such as Scala, Eiffel, and C++ (the last through virtual dispatch is performed. For instance, the problem appears if dispatch inheritance) [32, 27, 19]. is permitted on Java interfaces, as in JPred [22]. Next, diamond inheritance leads to (at least) two major prob- lems that have not been adequately solved: (1) determining how and when the superclass constructor/initializer should be called 3. Previous Solutions [37, 36], and (2) how to ensure non-ambiguity of multimethods in a modular fashion [30, 22, 3]. The first problem arises when the Object initialization. Languages that attempt to solve the object graph inheritance semantics is chosen, while the second appears initialization problem include Eiffel [27], C++ [19], Scala [32] and with either tree or graph semantics. Smalltalk with stateful traits [8]. In Eiffel, even though (by default) only one instance of the re- Object initialization. To illustrate the first problem, consider peatedly inherited class is included (e.g., Stream), when construct- Figure 1, which shows a class hierarchy containing a diamond. Sup- ing an InputOutputStream, the Stream constructor is called pose that the Stream superclass has a constructor taking an integer, twice. This has the advantage of simplicity, but unfortunately it to set the size of a buffer. InputStream and OutputStream call does not provide the proper semantics; Stream’s constructor may this constructor with different values (1024 and 2048, respectively). perform a stateful operation (e.g., allocating a buffer), and this op- But, when creating an InputOutputStream, with which value eration would occur twice. should the Stream constructor be called? Moreover, InputStream In C++, if virtual inheritance is used (so that there is only and OutputStream could even call different constructors with dif- one copy of Stream), the constructor problem is solved as fol- fering parameter types, making the situation even more uncertain.

View Full Text

Details

  • File Type
    pdf
  • Upload Time
    -
  • Content Languages
    English
  • Upload User
    Anonymous/Not logged-in
  • File Pages
    13 Page
  • File Size
    -

Download

Channel Download Status
Express Download Enable

Copyright

We respect the copyrights and intellectual property rights of all users. All uploaded documents are either original works of the uploader or authorized works of the rightful owners.

  • Not to be reproduced or distributed without explicit permission.
  • Not used for commercial purposes outside of approved use cases.
  • Not used to infringe on the rights of the original creators.
  • If you believe any content infringes your copyright, please contact us immediately.

Support

For help with questions, suggestions, or problems, please contact us