II. Co-contextual Type Checkers for Functional Languages
9. Co-contextual Featherweight Java
9.5. Performance Evaluation
9.5.2. Evaluation on real Java program
Input data. We conduct an evaluation for our co-contextual type checking on realistic FJ programs. We wrote about 500 SLOCs in Java, implementing purely functional data structures for binary search trees and red black trees. In the Java code, we only used features supported by FJ and mechanically translated the Java code to FJ. For evaluating the incremental performance, we invalidate the memoized results for the threeNat classes as in the experiment above.
Experimental setup. Same as above.
Results. We show the measurements in milliseconds for the 500 lines of Java code. javac / contextual co-contextual init co-contextual inc
Our own non-incremental contextual type checker is surprisingly fast compared to javac, and not even our incremental co-contextual checker gets close to that performance. When comparing javac and the co-contextual type checker, we observe that the initial perfor- mance of the co-contextual type checker improved compared to the previous experiment, whereas the incremental performance degraded. While the exact cause of this effect is unclear, one explanation might be that the small input size in this experiment reduces the relative performance loss of the initial co-contextual check, but also reduces the relative performance gain of the incremental co-contextual check.
10. Co-Contextual Featherweight Java
with Generics
In this chapter, we extend FJ with generics (FGJ) and co-contextualize it. Generic types allow programmers to implement algorithms in a type-safe manner and avoid the usage of class casts. For example, to provide generic list type structures that can be used for all types of list elements. Therefore, FGJ is a very powerful language extension to FJ, which makes the programmers life easier. However, it is challenging to co-contextualize FGJ because of the generic types and the impact they have in performing the operations of merging and removing class table requirements.
We start by describing the traditional FGJ, i.e., the types, syntax, contexts and judgements used while type checking, and then provide the contextual typing rules. Next, we present the co-contextual structures for FGJ and describe the syntax and the changes to the requirements sets and judgements. Then, extend the operations on the class table requirements in the presence of generic types. Finally, we construct the co-contextual typing rules for FGJ by applying dualism.
10.1. Featherweight Java with Generics
FGJ was introduced in the same paper as FJ [IK01]. In FGJ, classes and methods have generic type parameters, represented by angle braces (< and>). Hence, a single declaration of a method or class corresponds to a set of related methods with different signatures or a set of related types, respectively, depending on the usages of the generic types. More specifically, generic methods are those methods that are written as a single method declaration and can be called with arguments of different types. The type checker ensures the correctness of whichever type is used. To give an intuition of the generic classes and methods let us consider the following example:
class Pair <X extends Object, Y extends Object> extends Object { X fst;
Y snd;
Pair(X fst, Y snd) {
super(); this.fst=fst; this.snd=snd; }
<Z extends Object> Pair<Z,Y> setfst(Z newfst) { return new Pair<Z,Y>(newfst, this.snd); }
The class P air is parametrized over the generic types X and Y . The fields of this class fst and snd have as types X and Y , respectively. Different objects of P air can have different instantiations of X and Y . A generic type can be substituted to different types. Consequently, fst and snd have different types corresponding to X and Y . For example, fst can be used in the program with types Int or String, obtained from
(new Pair<Int, Int>(1, 2)).fstor (new Pair<String, String>(hello, world)).fst, respectively.
Similar to the class P air, the method setfst is parameterized over the generic type Z. Depending on the usage of Z in method invocations the method will return different result types. For example the invocation(new Pair <Int, Int> (1,2)).setfst <Int> (2), yields a result of typePair <Int, Int>, while(new Pair <Int, Int> (1,2)).setfst <String> (hello)yields a result of typePair <String, Int>.
T ::= X | N Types
N ::= C<T> Nonvariable types
L ::= class C<X / N>/ N {T f ; K M } class declaration
K ::= C(T f ){super(f ); this.f = f } constructor
M ::= <X / N> T m(T x){ return e; } method declaration e ::= x | this | e.f | e.m<T>(e) | new N (e) | (N )e expression
Γ ::= ∅ | Γ; x : C | Γ; this : C typing contexts
∆ ::= ∅ | ∆; X / N bounds
Figure 10.1.: FGJ syntax, typing contexts, and bounds.
In the following, we recap the FJ syntax and typing rules required to support generics. The syntax for FGJ is illustrated in Figure 10.1. The types of FGJ extend the FJ types with generic types, where X denotes type variables and N ranges over nonvariable types. N represents classes, which are parametrized over other types; type variables or nonvariable types. L represents class declarations. Classes are parametrized over generic types, i.e., bounded type variables. These type variables can be used as types for the member fields of the given class, as shown in the example above (X fst ). The relation between type variables X and their bounds N is represented via the symbol /, which stands for extends in FJ. Similar to classes, methods (M ) are parametrized over type variables. Generic type parameters (<X / N>) of a method can be used as part of the method signature, serving as types of the method parameters. Regarding expressions, there is a change at method invocation and object creation. In case of method invocation, while calling a generic method its generic parameters are instantiated from type variables to concrete types. In the case of the new expression, the class N is a nonvaraible type, i.e., if the class has generic parameters, then while calling the constructor of that class the generic parameters are instantiated.
In FGJ, the type variables used to parametrize classes and methods are bounded. This information is stored to an additional context to Γ and class table CT , which is denoted by ∆. Bounds of the types are retrieved using the following auxiliary function:
10.1. Featherweight Java with Generics bound∆(N ) = N
The bound function applied to a type variables returns the bounds of the type variables by looking them up in ∆. However, bound applied to a nonvariable type returns the type itself.
FGJ introduces a new judgement for type well-formedness ∆ ` T ok, in addition to the typing and subtyping judgements, which are used in FJ.
WF-Object ∆; CT ` Object ok WF-Var X ∈ dom(∆) ∆; CT ` X ok WF-Class ∆; CT ` T ok ∆ ` T <: [T /X]N extends(C <X / N>, CT ) = N ∆; CT ` C <T> ok
Figure 10.2.: Well-Formedness Rules.
Figure 10.2 illustrates the rules for well-formed types. In contrast to the original well-formedness rules, we make the class table CT explicit. If the declaration of a class C begins class C <X / N>, then the type C <T> is well-formed if X substituted by T respects the bounds N i.e., if T <: [T /X]N . We write ∆ ` T ok if type T is well formed in the context ∆.
Subtyping is as in FJ, with the difference that the type checker uses the information provided by ∆ in addition to the information provided by the class hierarchies in a class table to solve subtyping constraints.
In the following, we describe the typing rules of FGJ, considering only the changes we did to the contextual typing rules of FJ to support FGJ. All typing judgements of FGJ, in contrast to FJ, have an additional context to Γ, CT , which is ∆ to keep track of the bounded types.