Generalized Algebraic Data Types and Object-Oriented Programming
Andrew Kennedy Claudio V. Russo [email protected] [email protected] Microsoft Research Ltd, 7JJ Thomson Ave, Cambridge, United Kingdom
ABSTRACT 1. INTRODUCTION Generalized algebraic data types (GADTs) have received Consider implementing a little language using an object- ] much attention recently in the functional programming com- oriented programming language such as Java or C . Ab- munity. They generalize the (type) parameterized algebraic stract syntax trees in the language would typically be repre- datatypes (PADTs) of ML and Haskell by permitting value sented using an abstract class of expressions, with a concrete constructors to return specific, rather than parametric, type- subclass for each node type. An interpreter for the language instantiations of their own datatype. GADTs have a number can be implemented by an abstract ‘evaluator’ method in the of applications, including strongly-typed evaluators, generic expression class, overridden for each node type. This is an pretty-printing, generic traversals and queries, and typed instance of the Interpreter design pattern [6]. LR parsing. We show that existing object-oriented program- For example, take a language of integer, boolean and bi- ming languages such as Java and C] can express GADT defi- nary tuple expressions: nitions, and a large class of GADT-manipulating programs, exp ::= con | exp + exp | exp - exp | exp == exp through the use of generics, subclassing, and virtual dis- | exp && exp | exp || exp | exp ? exp : exp patch. However, some programs can be written only through | (exp, exp) | fst(exp) | snd(exp) the use of redundant runtime casts. Moreover, instantiation- specific, yet safe, operations on ordinary PADTs only admit C] code to implement this abstract syntax and its interpreter indirect cast-free implementations, via higher-order encod- is shown in Figure 1. Note in particular two points. First, ings. We propose a generalization of the type constraint the result of the eval method has the universal type object, ] mechanisms of C and Java to both avoid the need for casts as expressions can evaluate to integers, booleans or pairs. in GADT programs and higher-order contortions in PADT Second, evaluation can fail due to type errors: adding an programs; we present a Visitor pattern for GADTs, and de- integer to a boolean throws InvalidCastException. scribe a refined switch construct as an alternative to virtual Now suppose that we decide to add static type-checking dispatch on datatypes. We formalize both extensions and to the language, for example checking that arithmetic op- prove type soundness. erations are applied only to integer expressions and that conditional expressions take a boolean expression as condi- Categories and Subject Descriptors tion and two expressions of the same type for the branches. D.3.3 [Programming Languages]: Language Constructs We could easily add a method that checks the type of an and Features— constraints, data types and structures, poly- expression. This would then assure us that evaluation can- morphism, classes and objects, inheritance; F.3.3 [Logic not fail with a type error; however, the runtime casts in the evaluator code (e.g. (int) in Plus.Eval) are still necessary and Meanings of Programs]: Studies of Program Con- ] structs—type structure, object-oriented constructs to convince C of the safety of the evaluator. Now consider building types into the AST representation itself, using the generics feature recently added to C] and General Terms Java to parameterize the Exp class by the type of expressions Languages, Theory that it represents. Then we can: • define Exp
] Permission to make digital or hard copies of all or part of this work for Figure 2 lists C code that does just this. Observe how the personal or classroom use is granted without fee provided that copies are type parameter of Exp is refined in subclasses; moreover, not made or distributed for profit or commercial advantage and that copies this refinement is reflected in the signature and code of the bear this notice and the full citation on the first page. To copy otherwise, to overridden Eval methods. For example, Plus.Eval has re- republish, to post on servers or to redistribute to lists, requires prior specific sult type int and requires no runtime casts in its calls to permission and/or a fee. OOPSLA'05, October 16–20, 2005, San Diego, California, USA. e1.Eval() and e2.Eval(). Not only is this a clever use of Copyright 2005 ACM 1-59593-031-0/05/0010 ...$5.00. static typing, it is also more efficient than the dynamically- namespace U { // Untyped expressions namespace T { // Typed expressions public class Pair { public class Pair { public object fst, snd; public A fst; public B snd; public Pair(object fst, object snd) public Pair(A fst, B snd) { this.fst=fst; this.snd=snd; } { this.fst=fst; this.snd=snd; } } } public abstract class Exp public abstract class Exp
Figure 1: Untyped expressions with evaluator Figure 2: Typed expressions with evaluator typed version, particularly in an implementation that per- datatypes require either redundant runtime-checked casts forms code specialization to avoid the cost of boxing integers or awkward higher-order workarounds. To illustrate the and booleans [11]. problem, take the presumably simpler task of coding up The central observation of this paper is that the coding linked lists as an abstract generic class List> This = (Cons
>) (object) this; Figure 5: Equality on values, using constraints return This.head.Append(This.tail.Flatten()); } } tional type constraints on methods, as statically checked pre- Figure 3: Flatten on Lists, using casts conditions. Then adding the constraint where T=Pair
We have parameterized the visitor interface on the result • For each class D
Figure 13: Typed expressions in typed environments
A typing environment Γ has the form Γ = X , x :T , E usual, the reduction relation is defined by both primi- where free type variables in T and E are drawn from X . tive reduction rules and contextual evaluation rules. We write · to denote the empty environment. Judgement forms are as follows: All of the judgment forms and helper definitions of Fig- ures 15 and 16 assume a class table D. When we wish to be • The formation judgement Γ ` T ok states “in typing more explicit, we annotate judgments and helpers with D. D environment Γ, the type T is well-formed with respect We say that D is a valid class table if ` cd ok for each class to the class table and type variables declared in Γ”. definition cd in D and the class hierarchy is a tree rooted at object (which we could easily formalize but do not). • The formation judgement ` Γ ok states that “the types The operation mtype(T .m), given a statically known class in the environment are individually well-formed with T ≡ C
Figure 14: A GADT type-checker, recovering strongly-typed from untyped expressions (using switch) Syntax: (class def) cd ::= class C
X ` T , U , V ok X ∈ Γ class C
T =U ∈ Γ Γ ` T =U Γ ` C
Γ ` X ok Γ ` U =T Γ ` T =U Γ ` U =V (eq-refl) (eq-sym) (eq-tran) Γ ` X =X Γ ` T =U Γ ` T =V Subtyping:
Γ ` T =U Γ ` T <: U Γ ` U <: V X ∈ Γ D(C ) = class C
Γ ` e : T Γ ` T <: U Γ ` I ok fields(I ) = T f Γ ` e : T (ty-sub) (ty-new) Γ ` e : U Γ ` new I (e) : I
Γ ` e : I Γ ` T ok Γ ` e : [T /X ]U mtype(I .m) =
class C
` public virtual T m
class C
X ` I , T ok fields(I ) = U g f and g disjoint ` md ok in C
Figure 15: Syntax and typing rules for C] minor Operational Semantics: (reduction rules) 0 fields(I ) = T f mbody(I .m
0 0 e → e e → e (c-meth-rcv) 0 (c-meth-arg) 0 e.m
D(C ) = class C
Method lookup:
D(C ) = class C
mtype(C
D(C ) = class C
mtype(C
D(C ) = class C
mbody(C
D(C ) = class C
mbody(C
Figure 16: Evaluation rules and helper definitions for C] minor
Now some comments on the differences in our rules. Apart general unifier of the equational hypotheses and testing that from the usual equivalence and congruence rules, the type the equated types are identical. equivalence judgement includes the novel hypothesis and Our subtyping judgement Γ ` T <: U is standard, except decomposition rules, (eq-hyp) and (eq-decon), discussed at that the usual reflexivity rule is generalized by rule (sub-refl) length in [3]: rule (eq-decon) states that class names are in- to include equivalent (not just identical) types in the sub- jective so that we can deduce the equivalence of correspond- typing relation. Rule (ty-sub) can exploit these equations ing type arguments from the equivalence of two instantia- to assign more than one type to an expression (even if these tions of the same class. Full reflexivity is derivable from types are otherwise unrelated by the class hierarchy). reflexivity on variables and the congruence rules. It is easy Rule (ty-meth) imposes an additional premise: the ac- (and useful) to show that, in the context of no equations, tual, instantiated constraints of the method signature (if X ` T =U implies T = U (syntactically). any) must be derivable from the equations in the context. Cheney and Hinze also prove the following pragmatically In turn, rules (ok-virtual) and (ok-override) add the declared useful result that relates equational reasoning in a satisfiable or inherited formal method constraints to the environment, environment to unification (Proposition 1. of [3]): before checking the method body: the body may assume the constraints hold, thus allowing more code to type-check. Theorem 1. Assume ` X , E ok and let Θ ≡ [U /X ] be Our type checking rules are not algorithmic in their cur- = any most-general unifier of E, then X , E ` T1 T2 if, and rent form. In particular, the rules do not give a strategy only if, Θ(T1) = Θ(T2) (syntactically). for proving equations or subtyping judgements and the type If our hypothetical equations are satisfiable, then we can checking judgement for expressions is not syntax-directed decide whether an equation is derivable by applying a most because of rule (ty-sub). As a concession to producing an algorithm, rules (ok-virtual) and (ok-override) require that Lemma 5 (Substitution for terms). If Γ, x:T ` e : the declared constraints in E are satisfiable. This ensures T and Γ ` v : T then Γ ` [v/x ]e : T . that an algorithm will only have to type check the bodies of methods that have (most general) solutions. This does Proof. By induction on the typing derivation. not rule out any useful programs: methods with unsatisfi- able constraints are effectively dead, since the preconditions To prove Preservation we also need the following proper- for calling them can never be established. Rejecting un- ties of inheritance: satisfiable methods means that it should be easy to adapt an existing type checking algorithm for C] minor without Lemma 6 (Field Preservation). Provided D is a valid constraints: the modified algorithm first solves the equa- class table, · ` T , U ok and · ` T <: U , then fields(U ) = tions to obtain a most general unifier, applies that unifier U g and fields(T ) = T f implies Ti = Ui and fi = gi for to the context, body and return type, and then proceeds all i ≤ |g|. with type checking as usual. Under the unifier, types that are equivalent must be syntactically identical (by Theorem Lemma 7 (Signature Preservation). Provided D is 1), which makes subtyping and equivalence simple to test. a valid class table, · ` T , U ok and · ` T <: U then At a method call, equational pre-conditions will either be mtype(U .m) =
• The relations mtype(T .m) = and fields(T ) = de- Theorem 2 (Preservation). If D is a valid class ta- fine partial functions. 0 ble and · ` e : T then e → e implies · ` e : T . • If · ` T ok and fields(T ) = T f then f are disjoint Proof. Assume D is a valid class table and prove and · ` T ok. 0 0 e → e =⇒ ∀T . · ` e : T =⇒ · ` e : T • If · ` T ok and mtype(T .m) =
• mtype(I .m) =
• mtype(I .m) is undefined then mtype(([U /Y ]I ).m) is • mtype(U .m) =
0 runtime instantiation T of C is used to instantiate the type Theorem 4 (Type Soundness). Define e →? e to be 0 parameters of the branch; the scrutinee value replaces the the reflexive, transitive closure of e → e . If D is a valid pattern variable. Rule (r-fail) discards the first branch of class table and · ` e : T , e →? e0 with e0 a normal form, 0 0 the switch, when the type of the scrutinee does not derive then either e is a value with · ` e : T , or a stuck expres- from C . Rule (r-default) returns the default value, when sion of the form E[(U )new I (v)] where ¬ ` I <: U . the list of branches has been exhausted. So, if the scrutinee 0 Proof. An easy induction over e →? e using Theorems evaluates at all, the branches are tested in sequence, until 3 and 2. either one is taken, or we proceed with the default. Provided we extend the definition of evaluation contexts 5.1 Formalizing switch of Section 5 to cater for switch expressions, We now show how to formalize a simple variant of the E ::= ... | switch (E) {b default:e} switch statement described informally in Section 3.4. Be- cause C] minor only has expressions, it is more convenient then we can again re-establish: to formalize a switch expression, whose branches return a Theorem 5 (Type Soundness). C] minor, extended value, but the principles are similar. For brevity, we formal- with equational constraints and switch expressions, remains ize a unary switch that dispatches on a single expression; n-ary switches are an easy exercise. sound, in the sense of Theorem 4. Figure 17 summarizes the additions. In the switch expres- Again, the satisfiability requirement is only imposed as a sion switch (e1) {b default:e2}, we call e1 the scrutinee, concession to producing a type checking algorithm that need the possibly empty sequence b branches, and e2 the default not attempt to type statically dead branches under nonsen- expression. A branch case C
Γ ` e1 : D
` I <: C
6 ∃T. ` I <: C
0 |b| = 0 e1 → e1 (r-default) (c-switch) 0 switch (v) {b default:e} → e switch (e1) {b default:e2} → switch (e1) {b default:e2}
Figure 17: Extensions to C] minor for switch constructors’ [25], and were conceived as a means of repre- In this paper we described how the existing generics ca- senting types at runtime [5]. Independently, Cheney and pability of C] and Java can be used to write only a limited Hinze formulated a very similar system, which they call class of functions expressed over GADTs, namely those that ‘first-class phantom types’ [3]. Our formal characterization are ‘generic’ in the type parameters of the GADT. A similar of type equality is similar to theirs; in particular, the use of restriction applied to inductive types in an early version of a decomposition rule. In contrast, Xi, Chen and Chen use the Coq proof assistant [15]. Its elimination rule for induc- a semantic notion of entailment, and then show that a rule- tive types used substitution on types, just as the rule for based characterization of the mixed-prefix unification algo- subclassing and signature refinement does in C] and Java. rithm is sound and complete with respect to the semantics. More recent versions of Coq support a more expressive pat- Hinze’s subsequent tutorial article on phantom types [8] is tern matching construct that uses unification to solve type a rich source of examples. equalities [4], providing similar power to our proposal for Type inference for GADTs has received some attention re- equational constraints, but in a much richer setting. cently. Simonet and Pottier cast it in the framework of the HM(X) constraint-based generalization of Hindley-Milner type inference for ML [23], and show that when ‘X’ is type 7. CONCLUSION AND FUTURE WORK equality, with suitable annotations on case constructs, type We have shown how the combination of generics, sub- inference reduces to a decidable constraint solving problem. classing, and virtual dispatch supports the definition and Peyton Jones, Washburn and Weirich take a different tack, limited use of generalized algebraic data types in object- solving unification-like problems on-the-fly. Their type sys- oriented programming languages. To achieve full expres- tem uses most general unifiers, avoiding equations [17]. sivity for GADTs in C] we have proposed the addition of GADTs can be seen as a step towards full-blown depen- equational constraints to the language; this extension also dent types in programming. Sheard explores this connection makes it much easier to code certain operations over ordi- with his recent programming language Omega [22, 21], us- nary algebraic datatypes such as Flatten and Unzip on lists. ing GADTs to express strong invariants over data structures For complex examples, virtual dispatch is impractical, and such as binomial heaps and red-black trees. so for convenience we have proposed a generalization of the Until now there has been no work on GADTs in the con- switch statement. Although we have not discussed this, our text of object-oriented programming. Sestoft noticed that extensions have a natural erasure semantics, making them C] generics can be used to express typed expressions and ] compatible with the erasure-based generics of Java as well evaluation [19]; independently Meijer [12] presented a C as the runtime-type passing semantics of C]. translation of the typed expression example from [17]. We plan to implement our extensions in a compiler for C] There have been a number of proposals to extend OO lan- version 2.0; we do not foresee any difficult interactions with guages like Java with (non-generalized) algebraic datatypes features from the complete language. [14, 26, 13]. Some of these proposals support the features Future work includes studying formal translations from (2: non-regular recursion) and (3: existential type parame- System F with GADTs, into C] minor, with and without ters) identified in Section 2.2, but restrict ‘case’ classes to equational constraints, and extending the previous transla- take the same type parameters as their superclass. tion of Kennedy and Syme [10]. We have begun looking at two variants of System F: one, with full support for GADTs, object-oriented language. Concurrency and in the style of [25, 18, 17, 3], and utilizing equational con- Computation: Practice and Experience, 16:707–733, straints in the elimination rule for case (strong-case); the 2004. other, without equations, but with a case-elimination rule [11] A. J. Kennedy and D. Syme. Design and based on substitution (weak-case). The latter system char- implementation of generics for the .NET Common acterizes precisely the GADT-manipulating programs which Language Runtime. In Programming Language Design can be written in unextended C] without the use of runtime and Implementation. ACM, 2001. casts. Interestingly, the use of type-decomposition in deriv- [12] E. Meijer, November 2004. Presentation to IFIP WG ing equation judgments turns out to be crucial: given a term 2.8 (private communication). that makes use of equations and the strong-case rule, but [13] M. Odersky, P. Altherr, V. Cremet, B. Emir, containing no use of decomposition, there exists an equiva- S. Micheloud, N. Mihaylov, M. Schinz, E. Stenman, lent term derivable in the weak-case system without the use and M. Zenger. The Scala language specification, of equations at all. 2005. Available from http://scala.epfl.ch/. We are also investigating generalizing constraints still fur- [14] M. Odersky and P. Wadler. Pizza into Java: ther, specifying arbitrary subtype constraints on methods. Translating Theory into Practice. In 24th ACM This could subsume upper bounds, lower bounds, and equa- Symposium on Principles of Programming Languages tions. In particular, we would like the equivalence induced (POPL), pages 146–159, 1997. by subtyping (T =∼ U iff T <:U and U <:T ) to coincide [15] C. Paulin-Mohring. Inductive definitions in the system with our equivalence relation as axiomatized in Figure 15. Coq: Rules and properties. Technical Report 92-49, As here, the use of a decomposition rule is crucial: from Laboratoire de l’Informatique du Parall´elisme, Ecole C