Using Kinds to Type Partially-Polymorphic Methods
Total Page:16
File Type:pdf, Size:1020Kb
Electronic Notes in Theoretical Computer Science 75 (2003) URL: http://www.elsevier.nl/locate/entcs/volume75.html 20 pages Using kinds to type partially-polymorphic methods Daniel Bonniot 1 INRIA Rocquencourt France Abstract We extends type constraints with kind constraints to increase the expressiveness of constraint-based nominal type systems. In the context of object-oriented program- ming languages, they offer an alternative to the use of F-bounded polymorphism to type homogeneous binary methods. We also introduce the notion of partially poly- morphic methods to describe a common situation in object oriented hierarchies, and show how these can be typed in a modular fashion with kind constraints. 1 Introduction Static typing provides early detection and prevention of errors, resulting in increased software quality and lower production costs. Since the set of error- free programs is undecidable for Turing complete programming languages, type systems can only define an approximation of error-free programs —that is, a subset of them, to remain safe. However, one may also be less ambitious, and let some kind of anomalies, such as downward casts, exhaustiveness of pattern matching or exceptions handlers, be only detected dynamically. Each of these anomalies can then be tackled separately, either by some other form of static analysis or by a specific extension to the type system. Functional style and object-oriented style are two of the most popular programming paradigms at the moment. However, their type systems have been developed in rather different directions. While functional languages, as incarnated by ML, are based on the polymorphic, unconstrained Hindley- Milner type system, type systems for object-oriented languages such as Java or C# often lack polymorphism and higher-order functions but include sub- typing. Interestingly, many of the proposals to overcome the limitations of these systems can be seen as attempts to bridge the gap between the two: the 1 Email: [email protected] c 2003 Published by Elsevier Science B. V. 21 Bonniot addition of parametric types to Java [6,7,13], or conversely, the addition of object-oriented features to functional languages [16,17]. Extending polymorphic types with subtyping constraints [14,5,3] is more expressive than both systems, thus allowing to type (and statically check) more useful programming idioms. Since the quest for expressiveness is endless, these systems have certainly their own limitations. However, since most of them remained at best at the level of prototypes, their limitations have not yet all been found. In this paper, we focus on two problems that arise in practice from the interplay of polymorphism and subtyping, and we propose a single solution to solve them both. The rest of this paper is organized as follows: Section 2 presents several typing challenges that motivate the introduction of kinds in type systems with subtyping; Section 3 proposes a simple solution that however fails to achieve separate type-checking of modules; Section 4 presents a more evolved a solution that solves this remaining problem; Section 5 contains our formal type system with kinds; Section 7 compares to related work. 2 Motivations We start by examining several challenging type-checking situations of practical importance. Throughout the paper we consider type systems with nominal subtyping. Specifically, our examples use class declarations to declare new type names, for which subtyping is determined by the inheritance hierarchy. 2.1 Typing homogeneous operations We first consider the problem of typing homogeneous operations. Homoge- neous operations are a specific sort of binary (or n-ary) operations, character- ized by the shape of their domain. They accept several types as arguments. However, values of different types cannot be mixed in the same call [12]. A typical example is the comparison operator less: it can be applied to two strings, two integers, two dates, etc, but it should be ill-typed to mix any two of these types in a call. We shall examine how it is possible (or not) to express this requirement in several type systems. Monomorphic type system The program of figure 1 is an attempt to type less in a monomorphic type system, using Java syntax. The type system cannot prevent the intermixing of arguments of different types. Consequently, it is necessary to handle the case where a String is compared with an arbitrary value of type Comparable. Typically, this is done by runtime type inspection. In this case, it would be possible to return false, but since the comparison of a String and, say, a Date never makes sense, it is probably better to raise a runtime exception if values of different “kinds” are compared. Our aim is precisely to statically rule out 22 Bonniot abstract class Comparable { boolean less(Comparable other); } class String implements Comparable { boolean less(Comparable other) {// compares a string to a Comparable } } class Date implements Comparable { boolean less(Comparable other) { ... } } Fig. 1. Monomorphic language these cases. F-bounded polymorphic type system F-bounded polymorphism [8] extends bounded polymorphism by allowing the bound of a variable to refer to the type variable being bound. It offers a solution to type homogeneous operations [12]. abstract class Comparable<T> { boolean less(T other); } class String implements Comparable<String> { boolean less(String other) { ... } } class Date implements Comparable<Date> { boolean less(Date other) { ... } } We shall now propose a different solution to this typing problem. The comparison of our system with F-bounded polymorphism will be made in Section 7. Introducing kinds What other path could we follow to solve this typing problem? Between the monomorphic type (Comparable, Comparable) → boolean for less, which is too loose, and the F-bounded polymorphic type ∀T ≤ Comparable<T>. (T, T) → boolean, which is unintuitive, we could have considered the sim- pler bounded polymorphic type ∀T ≤ Comparable. (T, T) → boolean.How- ever this does not work: it is in fact equivalent to the monomorphic type, since intuitively, T can be instantiated by the type Comparable. In fact, String and Date share the property of being comparable, without 23 Bonniot having a common super-type. Therefore Comparable should not be a type, but a property possessed by some types. In other words, Comparable is a kind. We shall write Date : Comparable or Date implements Comparable to express that Date is a type of kind Comparable. We can now present an informal solution using kinds. kind Comparable; <T : Comparable> boolean less(T,T); class String implements Comparable { boolean less(String other) { ... } } class Date implements Comparable { boolean less(Date other) { ... } } Since T can be instantiated to either String or Date, pairs of strings or pairs of dates can be compared. Furthermore, no valid instantiation for T is a super-type of both String and Date, which prevents intermixing. This type therefore fulfills the requirements. In addition, we believe it is less involved than the F-bounded solution. Kinds are also appropriate to typecheck more complex cases of homoge- neous operations. For instance Litvinov [12] argues that it is sometimes useful to have a class inherit from another, while not being a subtype so that ho- mogeneous operations do not accept intermixing the super and the subclass (typically, PointsareColorPoints when it does not makes sense to compare one of each class). Their solution is to parameterize both classes and use F- bounded quantification. Our solution, using kinds, is to create a common su- perclass AbstractPoint containing the features to inherit (the x and y fields), and two classes Point and ColorPoint that implement the kind Comparable. Since AbstractPoint does not implement Comparable, intermixing is pre- vented. Our approach avoids again the “fake” and cumbersome parameteri- zation. Furthermore, the creation of the class AbstractPoint makes obvious thefactthataColorPoint is not a Point. In the F-bounded version, this fact is not immediately apparent: one must actually try to prove the subtyping —and fail— to conclude that it does not hold. We believe that this makes F-bounded quantification too complex for a widespread use in programming languages. The full code for both solutions is given in Appendix A. 2.2 Partially polymorphic functions So far we used kinds to describe a common property of unrelated types. One question immediately follows: how do kinds interact with subtyping? Given a class A of kind K and a subclass B of A, should then B always be of kind K? Actually, a function of type ∀T : K. T → T can always take an argument whose 24 Bonniot type B is a subtype of type A of kind , since by subsumption the argument is also of type A. However, using subsumption, we can only conclude that the type of the result is A.Conversely,ifB itself was of kind K, then we could type the application by instantiation of T by B, which would give to the result the more precise type B. We shall now see that this is sometimes desired to have the less precise result type: many functions have type that are more precise than A → A, but less precise than ∀T ≤ A. T → T. We call these functions “partially polymor- phic”, and now give several examples. Numerical operations Consider the following numerical hierarchy: Float class Float {...} { } class Integer extends Float ... Integer class Int32 extends Integer {...} class BigInt extends Integer {...} Int32 BigInt What is the type of the addition on numbers? The sum of two floats is a float, the sum of two integers is an integer, the sum of a float and an integer is a float. More generally, the type of the sum of two numbers is their least upper bound (Statement 1). The monomorphic or Hindley-Milner type systems do not allow to capture all possible types described by Statement 1 in a single type expression.