Visitor-Oriented Programming

Thomas VanDrunen Jens Palsberg Purdue University UCLA Computer Science Department Department of Computer Science 4531K Boelter Hall West Lafayette, IN 47907 Los Angeles, CA 90095 [email protected] [email protected]

ABSTRACT tionality. This way we add behavior modularly. However, Multiple dispatching and the visitor pattern are approaches while introducing the benefit of to to making object-oriented programs more extensible. Both an object-oriented setting, we are also importing the liabil- have a flavor of , thereby moving object- ity. Now we can no longer add classes easily. To use visitors oriented programming closer to functional programming. The for extension, we must in advance give each class an accept key idea of these approaches can be crystallized as a notion method (not to mention making sure that the visitor will of visitor which lies between functions and objects. Can have access to all internal data necessary for its task). More this idea be developed into a new form of visitor-oriented seriously, a visitor must know all the classes on which it will programming which combines the best of functional and operate [11]. The visit and accept methods are necessary object-oriented programming? As a first step, we present because using visitors is essentially using dynamic double a visitor calculus in which each value is a visitor and ev- dispatch— the correct method is selected based on both the ery visitor call uses double dispatching. We illustrate the acceptor’s class and the visitor’s class. Most object-oriented relationships to other paradigms by translating the lambda- languages support only single dispatch. calculus to the visitor calculus, and the visitor calculus to What we notice is that while a visitor is in fact an object, Java. Our calculus forms the core of a language in which we in a sense it can also be viewed as a function. A visitor is have programmed the translation of the lambda-calculus to related to both object-oriented and functional programming. the visitor calculus itself. To demonstrate the expressiveness We see a spectrum: of visitors, we show the translation in four versions that use Spectrum: function — visitor — object. smaller and smaller subsets of the language. Along the way, Is there a way we can exploit this approach to system build- we present correctness proofs and examples of running an ing so that we have the advantages of both paradigms, but implementation of our language. the disadvantages of neither? What we would like is a lan- guage designed specifically for programming with visitors, 1. INTRODUCTION where the visitor is the basic unit and the need for accept methods and precognition of acceptor classes is subsumed 1.1 Background by the language semantics, including rules for double dis- A program is a set of datatypes and a set of behaviors over patch. In other words, we would like a language where both cases of those datatypes. Maintaining a program requires datatypes and behaviors can be added modularly. extending the datatypes and their cases and extending the Various systems have been developed to study double dis- behaviors over them. We would like to to do this modu- patch and the visitor pattern. Millstein and Leavens [8] larly, that is, without recompiling the entire system each designed the Tuple language to show how to add multiple time an extension is made. This represents a well-known dispatch to conventional object-oriented languages cleanly, way of comparing programming paradigms and a dilemma letting the receiver of a method call be a tuple of objects in design [12, 3, 7, 15]. In object- rather than a single object and selecting a method most spe- oriented languages, we can easily extend the datatypes by cific to those objects. If there is no appropriate method or writing new classes, including subclasses. Extending behav- no most specific method, such a call causes a message not ior, however, is difficult since methods are members of a understood or message ambiguous error, respectively. The class and so cannot be changed or added to without recom- second of these errors is a result of the dispatch being sym- piling that class. Functional programming takes the other metric. Generalizing to multimethods (any methods where side of the trade off. There, new functions on a datatype can depends on more than one argument), a be written and compiled separately, but once a datatype is multimethod is symmetric if all dispatching arguments are created, its cases are fixed. treated uniformly, that is, no argument has priority for de- The visitor pattern [5] is an attempt to solve this prob- termining an appropriate method. lem in an object-oriented paradigm; that is, it is a way Millstein and Chambers [9] presented Dubious, an object- to add functionality modularly without changing the set of oriented core language with symmetric multimethods. Du- datatypes. If we have a set of classes and determine that we bious uses a module system to reason about extensibility need new functionality for them, instead of adding a method and features a static type system which ensures that pro- to each and recompiling, we write a visitor class, with a grams that type check do not throw message not understood visit method for each class, which will perform that func- or message ambiguous errors. Functions are objects and thus are first-class. Similarly, Clifton, Leavens, Chambers, the way, we present correctness theorems and examples of and Millstein [2] presented MultiJava, also with symmetric running an implementation of Peripaton. multimethods, but with a class system like that of Java. A feature of MultiJava is the open class, a class to which 1.2.1 A visitor calculus methods can be added without changing the class directly. In our visitor calculus, each value is a visitor and every This offers an alternative to the visitor pattern in that new visitor call uses double dispatching. The grammar for Visi- functionality can be added to a class without the the prior Calc is as follows: planning needed to make infrastructure for visitors. In order to capture the flavor of the visitor pattern it- p ::= (c, e) programs self, double dispatching should be asymmetric— if a visitor e ::= expressions does not have a method that matches the class of an accep- (e e) invocations tor exactly but inherits a method that does, that method is | new t[e] creations chosen over a method it has which matches an ancestor class | x field references to the acceptor. Asymmetric semantics eliminates the pos- | acceptor parameter references sibility of a message ambiguous error since the precedence c ::= t : t{x; m} classes among arguments disambiguates the method selection. Fur- m ::= (t 7→ e) method mappings thermore, a visitor class serving as an ancestor to all vis- A formal semantics will be presented in Section 3. For itors could contain a visit method for whatever class tops now, a VisiCalc program is a set of classes and a main ex- the class hierarchy (such as Object in Java; in a language pression. Each class defines a visitor type; a class definition designed specifically for visitors, this may be the visitor contains a name for the class, the class’s parent, a list of class itself) and ensure no message will not be understood. fields, and a list of methods. Methods do not have names, which is convenient for a core calculus and formal reasoning but are identified by the type of a dispatching parameter (we since eliminating the possibility of the computation causing can think of each method having the name visit, and there an error allows us to focus on the computation itself. ¿From can be at most one such method for any parameter type). a practical point of view, symmetric The parameter is referred to by acceptor; intuitively, the double dispatching may be preferable; however, in this pa- callee or receiver of the method has a visitor, and the pa- per, we will pursue the asymmetric variant, to keep it close rameter is the acceptor. (One could easily reverse this and to the visitor pattern. call the methods “accept” and the parameter “visitor,” if Dubious is classless but uses a module system that is good the reader finds that more in line with his experience with for what it formalizes—modules; a core calculus that focuses the visitor pattern.) on visitor behavior, however, would be more tractable with- When a method is called, (e1 e2), the method mappings out them and without the full programming language fea- for in the definition of e1’s class are searched for a method tures of MultiJava. We would further like a calculus that can matching e2’s type. If none are found, the methods of e1’s be related to both functional and object-oriented paradigms parent class are searched, and so forth. If no appropriate by encodings among representative languages, demonstrated method is found among e1’s class and all its ancestors, then by correctness proofs and implementations. we do a similar search for a method appropriate for e2’s Castagna, Ghelli, and Longo [1] studied the λ&-calculus of parent class. This entire process will terminate because we overloaded functions with multiple dispatching. While mo- assume a pre-defined class visitor that is at the top of tivated by object-oriented programming, the λ&-calculus is the class hierarchy, as Object in Java, which has a method purely functional. In the λ&-calculus, an overloaded func- appropriate for any parameter, returning that parameter. tion is essentially a set of functions, and an overloaded- Here is the translation of the λ-term λx.x: function call uses dispatching via the types of its members. Notice that functions are the primitive concept from which cla0: visitor { overloaded functions are constructed. While the λ-calculus (visitor -> acceptor ) is a sublanguage of the λ&-calculus, translating the λ&- } calculus to a language such as Java remains unexplored and seemingly difficult. As far as we know, the λ&-calculus has new cla0[] not been used as the core of a language. 1.2 Our results This and other code examples appear as they are produced 1 by our implementation of the translation algorithms which This paper presents a new calculus, VisiCalc and a larger we give formally in Section 2.1. It has one class, representing language, Peripaton, for which VisiCalc is a core. Both the single lambda, which has a method that simply returns hard-wire and formalize the visitor pattern. These languages its parameter. The main expression is an instantiation of will encourage the use of the visitor pattern as a founda- the class. To apply this, we use the main expression as tional programming paradigm. They also provide a frame- the visitor in an invocation; whatever the acceptor is, it will work for formal reasoning about the visitor pattern, modular surely be an instance of visitor, and so the method in cla0 extensibility, dynamic , and the relationship is called, returning the acceptor. between object-oriented programming and functional pro- Here is the translation of the visitor program into Java: gramming. We present translations of the λ-calculus to the visitor calculus, and of the visitor calculus to Java. Along 1Our VisiCalc is unrelated, but named in tribute, to Visi- Calc, the first commercial spreadsheet program, published by Software Arts. class visitor extends Object { p ::= c e programs visitor() {} e ::= expressions visitor accept(visitor x) { (e e) invocations return x.visit_visitor(this); | (e e < e >) invocations with extra, } non-dispatching parameters visitor visit_visitor(visitor acceptor) { | new t[e] creations return acceptor; | x variable references } | acceptor dispatching parameter visitor visit_cla0(cla0 acceptor) { | this self references return this.visit_visitor(acceptor); | let (x = e) in } e end let expressions } | {e}.x non-local field references class cla0 extends visitor { | gensym unique identifier creations cla0 (){ | if e = e then e super(); else e branch expressions } c ::= t : t{x; m} classes visitor accept(visitor x) { m ::= method mappings return x.visit_cla0(this); (t 7→ e) ordinary mappings } | (t < e >7→ e) mappings with extra, visitor visit_visitor(visitor acceptor) { non-dispatching parameters return acceptor; } } Figure 1: The Peripaton grammar new cla0() In object-oriented programming, visitors typically have a and object-oriented programming exists. Second, the lan- visit method and data structures have an accept method. In guage and the calculus are statically untyped and therefore VisiCalc, everything is a visitor and everything can be vis- no casts are needed. While unusual in an object-oriented ited. Therefore, the equivalent classes in Java have both setting, it is not without precedence, as both the Abadi- visit methods and an accept method. All classes in the Cardelli calculus and the language are not stati- translated system extend the class visitor. The object new cally typed. Third, although Peripaton allows self-reference cla0() is a visitor, and to visit an object (say, another new in the usual form of a this pointer, this construct is strik- cla0()), we call (new cla0()).accept(new cla0()). The ingly absent from the calculus. All three of these decisions accept method calls visit cla0(); cla0 has no such method have the same effect: they keep the language and espe- in its definition, but it inherits one from visitor which sim- cially the calculus as small as possible, and they anchor ply calls the visit method appropriate for the acceptor’s par- VisiCalc closer to the lambda calculus, rather than leaving ent class, visitor. Finally cla0.visit visitor() is called, it adrift as another object-calculus simply having visitor- returning its parameter, the original acceptor new cla(). like syntax. Different design decisions might be expected for a fully-featured language, but that is not our present 1.2.2 A language based on the calculus goal. There are, of course, other language features, such as To show how VisiCalc can serve as the core of a larger pattern-matching in ML [14] and Scala [13], which achieve language, we extend it to allow extra, non-dispatching ar- some of the same goals, but do not so clearly resemble a guments to parameters, self references, local variables (de- middle-ground between functional and object-oriented pro- fined in let expressions), non-local field references, unique gramming. identifiers, and branch expressions. The grammar for the Our language and calculus support single-inheritance hi- language, called Peripaton, is shown in Figure 1. erarchies. However, for the translation of lambda-terms to Note that x is now called a variable reference rather than VisiCalc, the full power of inheritance and dynamic dispatch a field reference. This is because in Peripaton variables can is not needed. Since the use of a type hierarchy is well also refer to local variables and extra parameters. For the known, our paper does not illustrate it further. sake of Peripaton, we assume unique identifiers are values We will later present a translation from VisiCalc to Feath- along with visitors. They are produced by gensym. The erweight Java [6] which, linked with our encoding of the λ- comparison in branch statements is defined only for unique calculus, provides a translation from the λ-calculus to Feath- identifiers; the comparison is true if both values originate erweight Java. Our translation does not use inner classes from the same evaluation of gensym. or continuation-passing style. It would have been straight- forward to map each function to a class with an “apply” 1.2.3 Discussion of the calculus and language method containing the function’s body, but that would re- Several design decisions deserve clarification before we quire nested classes to represent nested functions. If nested proceed. First, as mentioned earlier, asymmetric seman- classes are not allowed, a known solution to the problem of tics may not be desirable—always having a step that can translating a higher-order language to a first-order language be taken is not necessarily good, if that step is something is to use continuation-passing style transformation and clo- unintended by the programmer. However, a type system sure conversion [4]. Our translation does use a form of clo- which helps identify bugs is not our purpose here, but only sure conversion; a further investigation of the relationship to demonstrate that a middle ground between functional between our approach and others is left to future work. The rest of the paper Section 2 gives an extended example, a compiler written in Peripaton which translates (x) · Γ ` x ; Γ acceptor (1) from the lambda calculus to VisiCalc. This illustrates and motivates the programming paradigm, demonstrates the con- nection between functional programming and visitor-oriented (y) · Γ ` x ; Γx, y 6= x (2) programming, and leads the reader from Peripaton to Visi- Calc. Section 3 gives a formal specification of VisiCalc, in- 0 0 00 cluding a type system and a theorem of type soundness. ∆ · Γ ` M1 ; Γ e1 ∆ · Γ ` M2 ; Γ e2 00 (3) Section 4 builds the bridge to conventional object-oriented ∆ · Γ ` (M1 M2) ; Γ (e1 e2) programming by giving a translation from VisiCalc to Feath- 0 0 erweight Java and presenting theorems of type preservation (x) · Γ ` M ; Γ e α ∈/ Γ and behavior preservation. (y) · Γ ` λx.M ; Γ0 · α : visitor{fv(λx.M) (4) 2. FROM FUNCTIONS TO VISITORS (visitor 7→ e)} To illustrate and motivate the programming paradigm, we new α[[acceptor/y]fv(λx.M)] present a succinct but realistic example. A familiar use of 0 0 the visitor pattern is performing compiler passes on an inter- (x) · Γ ` M ; Γ e α ∈/ Γ fv(λx.M) = ∅ mediate representation of a program; for example, Gamma ∅ · Γ ` λx.M ; 0 et al. [5] use this in their introduction to the pattern. Con- Γ · α : visitor{(visitor 7→ e)} new α[] sider abstract syntax trees; many operations desirable in (5) the middle of a compiler can be implemented by a traver- This algorithm translates a program by building up a list sal of the tree (static checking, code transformation, pretty- of classes and producing a main expression. Each lambda printing, code generation, etc.). When visiting a node in term represents a new class in the list of classes and an the tree, the visitor can recursively visit the components expression creating an instance of that class. Intuitively, (children) of that node and use those results in computing functions become visitors. The free variables in the lambda the result at the current node. A new compiler pass can be term become fields in the class, and the argument to the written quickly by extending a general depth-first visitor, abstraction is the argument to a method in the class (i.e., inheriting code to perform the traversal— this is particu- acceptor). For this reason, we need to know the argument larly convenient for compiler passes that need to inspect to the nearest enclosing lambda term, if any, represented by only certain parts of the tree. This is also convenient for the metavariable ∆. maintaining the compiler if new constructs are added to the Rule (1) translates an occurrence of the current variable language: a new compiler can be constructed by extending to acceptor. Rule (2) leaves an occurrence of a non-current all the visitors with visitors that contain methods only for variable intact; it becomes a field reference. To translate an the new constructs. application, rule (3) translates the two composing lambda In this section we present a translation from a call-by- calculus expressions (each of which translation may have the value λ-calculus to VisiCalc. We first specify the translation side effect of producing some new classes, captured by the 0 00 formally and then implement it in our language Peripaton. use of Γ and Γ ) and creates an invocation composed of the To demonstrate the expressiveness of visitors, we present the two resulting visitor expressions; thus function calls are four versions of the implementation, written in smaller and translated to function calls. smaller subsets of Peripaton. The four programs are each The translation of an abstraction when a current variable 200–300 lines long; in this section we will show excerpts. is present in rule (4), results in a new class being added In the full version of the paper, which is available from our to the class list, as we anticipated above. Note that the webpage, there is an Appendix with all four programs in class hierarchy is flat, with all classes extending visitor. their entirety, including comments. The single method dispatches on visitor, meaning that this method will apply to any argument, as would be the case 2.1 Specification of the translation for the original lambda term. The body of that method is The metavariable ∆ stands for the “current” variable or, the translation of the body of the lambda term, and that if there is none, is empty. Γ is a list of VisiCalc classes. Even translation may itself produce new classes. The arguments though Γ contains the whole class, if α is the name of a class given to the constructor are the free variables of the term in Γ, we say α ∈ Γ for convenience. ∆·Γ ` M ; Γ0 e means (note that this excludes x) since the presence of a current that, given environment variables ∆ and Γ, lambda calculus variable implies that this will be a subexpression of a body expression M is translated to the list of VisiCalc classes of a method and so fields will be present. We need to re- 0 Γ and main VisiCalc expression e. fv(M) returns the free place the current variable with acceptor, however, since the variables of lambda calculus expression M. A variable is method parameter, rather than a field, is associated with it. free in an expression if it is not bound as the argument in Intuitively, the expression new α[. . . ] builds a closure that any lambda abstraction that occurs in the expression and represents λx.M. Rule (5) covers the case for a lambda encloses the variable. If a term has no free variables, then term where there is no current variable and the free variable it is closed. The notation [x/y]fv(M) means the list of free list is empty. (If there is no current variable, then we must variables in M with x substituted for y. be translating a top-level term, and if the program we are translating is a closed term, then the free variable list will be empty. The translation is not defined for a top-level term that is not closed.) In that case the constructor needs no arguments since the class has no fields. The second example is a term for application, λf.λx.fx. Goal : visitor { This takes two arguments (in curried form) and treats the expr; first as a function, applying the second to it. It is translated } to Abstraction : visitor { id; cla0: visitor { expr; f; } (visitor -> ( f acceptor )) Application : visitor { } expr1; cla1: visitor { expr2; (visitor -> new cla0[acceptor]) } } LambdaIdentifier : visitor { uid; new cla1[] }

Each lambda is translated to a class. One class is for the Since everything in Peripaton is a visitor, these classes inner lambda. The free variable is in the field. It takes a also extend visitor. They are used only for data, however, parameter and applies it to the field. The other class has a and contain no functionality. Goal stands for the entire pro- method that feeds its parameter to the constructor of a new gram, the root of the tree. Since a program in the lambda instance of the first class, making it the value of its field, calculus is simply an expression, Goal has one field, which just as applying a value to the outer lambda would make a stands for an expression. An expression can be an abstrac- closure for the inner lambda. tion, an application, or a variable. For these we have the Translating another familiar lambda term, the Y combina- classes Abstraction, Application, and LambdaIdentifier tor λf.(λx.f(x x) λx.f(x x)), may illuminate the translation (the last is so named in order to distinguish it from an iden- further: tifier in the target language). Peripaton is not typed, but if it were, the id field of Abstraction would be typed to cla0: visitor { be a LambdaIdentifier; it represents the bound variable in f; that lambda term, while expr represents the function body. (visitor -> ( f ( acceptor acceptor ))) Application has two fields representing the receiver and ar- } gument. Finally, LambdaIdentifier should have as its field cla1: visitor { a unique identifier as would be produced by gensym. f; We need another set of visitor classes to build an abstract (visitor -> ( f ( acceptor acceptor ))) syntax tree in the target grammar: } cla2: visitor { TargetGoal : visitor { (visitor -> ( new cla0[acceptor] classList; new cla1[acceptor])) expr; } } Class : visitor { new cla2[] name; parent; Note that cla0 and cla1 are identical. This can then be fieldList; hand-optimized to methodList; } cla1: visitor { Method : visitor { f; type; (visitor -> ( f ( acceptor acceptor ))) expr; } } cla2: visitor { Invocation : visitor { (visitor -> ( new cla1[acceptor] expr1; new cla1[acceptor])) expr2; } } Creation : visitor { new cla2[] name; exprList; We have not yet reasoned formally about the class unifica- } tion and dead code elimination that can be done to shorten AcceptorKW : visitor { } the program resulting from the translation. VisitorKW : visitor { } VisitorIdentifier : visitor { 2.2 Implementation in Peripaton uid; We now present the implementation of this encoding in } Peripaton. First we need a few classes to serve as data struc- tures representing the abstract syntax trees of the source These are not conceptually different from the earlier set, language: but they reflect how a program in a Peripaton-like language is a set of classes and a main expression; a class has a name, to arrive at the calculus, showing the code for translating the name of a parent class, a set of fields, and a set of meth- Application at each step. ods; and that a method is a dispatch type and a body. We The first victim is non-local field references. These refer- also require classes AcceptorKW and VisitorKW to stand in ences make code concise but do not add to the expressive for the keywords acceptor and visitor, since they can ap- power of the language. Indeed, some philosophies of soft- pear in the place of identifiers. ware engineering discourage non-local field reference, saying A visitor to perform the encoding will need methods to that fields ought to be retrieved by accessor methods. In handle each source construct. It also needs to maintain a lieu of non-local references, we must provide accessor meth- current variable (the one bound in the nearest enclosing ab- ods for the classes that make up the abstract syntax tree. straction) and a list of classes that have been generated so But what would these methods be like? Accessor methods far. These will be passed as extra parameters. Consider the typically take no parameters and have names similar to the method for handling an application: fields they retrieve. Peripaton methods have no name and require at least one parameter. EncodingVisitor : visitor { ... To emulate this functionality we propose a programming (Application -> idiom that uses accessor visitors— we create a new class that let t1 = (this {acceptor}.expr1 ), contains no functionality or data but is used only to trigger t2 = (this {acceptor}.expr2 an accessor method. When we wish to retrieve the value of ) a field, we make an invocation with a visitor of the accessor in class as the parameter. We can consider the parameter to be new TargetGoal[{t2}.classList, simply ignored— although in fact it is not ignored, since it new Invocation[{t1}.expr, is used for dispatching. In our example, we create a GetExpr {t2}.expr]] class and a corresponding method for access to the expr field end) of Goal: ... } Goal : visitor { expr; (GetExpr -> expr) Application indicates the type of the parameter; this } method will be called when a visitor of class Application or GetExpr : visitor {} any descendent is applied to a visitor of class EncodingVisitor or any descendent, assuming no more specific methods have To use this on a Goal stored in g, we write (g new been written. list the extra, non-dispatching pa- GetExpr[]). rameters which stand for the current variable and the list of This idiom fits nicely with the object-oriented perspective classes so far. of a method call being a “message send.” An object of the The first step in the computation is to apply the visi- getter class is a message; when a visitor visits that object, tor recursively to the first expression in the application. it responds to the message. Thus visitors can be compared Since acceptor refers to the parameter (known to be of to dynamic (or first-class) messages [10]. Observe the new type Application), we extract the first expression using code fragment: non-local field access, {acceptor}.expr1. We use this to apply the same visitor, passing extra arguments v and cl EncodingVisitor : visitor { ... unchanged. This invocation returns a TargetGoal, which is (Application -> useful not only to represent the finished product but also a let t1 = (this (acceptor new GetExpr1[]) current state in the translation process, as in this case. We ), store this in t1. t2 = (this (acceptor new GetExpr2[]) We want similarly to translate the other expression in the ) application. However, several more classes may have been in added to the class list as a side effect of the translation of new TargetGoal[(t2 new GetClassList[]), the first expression. We extract it from t1 and pass it as an new Invocation[ extra parameter in place of cl. We store the result in t2. (t1 new GetExpr[]), The return expression is a new TargetGoal. No classes (t2 new GetExpr[])]] are added at this point in the translation, but some may end) have been added in the translation of the second expression. ... Therefore we construct a TargetGoal with the classList } from t2 as its cl field. Its main expression, which concep- tually has the same effect as the expression we are translat- 2.4 Removing non-dispatching arguments ing in the source language, is an invocation using the main Another language feature that is convenient but adds more expressions from the two TargetGoals produced by the re- headache than expressiveness when it comes to formal rea- cursive applications of the visitor. soning is the extra, non-dispatching arguments. To compen- sate for eliminating them from the language, we make the 2.3 Removing non-local field references extra arguments into fields of the visiting class. When we We also use this example to build a bridge between the wish to invoke the visitor recursively but supply new extra full Peripaton language and the core calculus we will use arguments, we replace this (which would use the old fields) for formal reasoning later in the paper. In the rest of this with the creation of a new visitor, passing the extra argu- section we will remove constructs from Peripaton by steps ments to the constructor to be used as fields. Since we will not use this in the language from here on, we eliminate it v ::= new t[v] values also. A casualty of this is that class extension is less ex- pressive, since this will always be replaced by an instance We declare a pre-defined class: of the class defining that method rather than by a subclass on which this method may have been called. That does not visitor { (visitor 7→ acceptor) } come to play in our example. Observe the new code frag- To describe the operational semantics, we use the notion ment: of a context, i.e., an expression with a hole in it. The EncodingVisitor : visitor { ... metavariable X ranges over evaluation contexts, and the (Application -> notation Xhei means context X with its hole filled with e. let t1 = (new EncodingVisitor[v, cl] Operational Semantics: (acceptor new GetExpr1[])), t2 = (new EncodingVisitor[ find-method(c, t1, t2) = (t 7→ e) fields(c, t1) = x v, (t1 new GetClassList[])] (6) (acceptor new GetExpr2[])) (c, Xh(new t1[v1] new t2[v2])i → in (c, Xh[acceptor, x/new t2[v2], v1]ei new TargetGoal[(t2 new GetClassList[]), new Invocation[ Field lookup: (t1 new GetExpr[]), (t2 new GetExpr[])]] fields(c, visitor) = ∅ (7) end) ... 0 } t : t {x; m} ∈ c 0 fields(c, t ) = y (8) 2.5 Removing let expressions fields(c, t) = x, y As a final step, we remove let expressions and non-field local variables. This is a simple matter of inlining: Method lookup:

0 EncodingVisitor : visitor { ... t1 : t {x; m} ∈ c (t2 7→ e) ∈ m (Application -> (9) match(c, t1, t2) = (t2 7→ e) new TargetGoal[ ((new EncodingVisitor[ 0 t1 : t {x; m} ∈ c ∀(τ 7→ η) ∈ m v, τ 6= t2 ((new EncodingVisitor[v, cl] 0 00 (10) match(c, t , t2) = (t 7→ e) (acceptor new GetExpr1[])) 00 match(c, t1, t2) = (t 7→ e) new GetClassList[])] (acceptor new GetExpr2[])) t 6= visitor (11) new GetClassList[]), match(c, visitor, t) = • new Invocation[ ((new EncodingVisitor[v, cl] match(c, t1, t2) = (t2 7→ e) (acceptor new GetExpr1[])) (12) find-method(c, t1, t2) = (t2 7→ e) new GetExpr[]), ((new EncodingVisitor[ 0 t2 : t {x; m} ∈ c v, match(c, t1, t2) = • ((new EncodingVisitor[v, cl] 0 00 (13) find-method(c, t1, t ) = (t 7→ e) (acceptor new GetExpr1[])) 00 find-method(c, t1, t2) = (t 7→ e) new GetClassList[])] (acceptor new GetExpr2[])) We have been claiming that under the VisiCalc semantics no message not understood or message ambiguous error could new GetExpr[])]] 0 ever occur. We now verify that claim. We use t : t to ) 0 ... indicate that t is a child class of t . } Lemma 3.1. find-method will always return a method. As a final note, to attain a core calculus we also eliminate We define the depth of two classes t and s in c, ∆(c, t, s) gensym and the unique identifiers it produces. This does not to be the n from the proof of Lemma 3.1. affect the fragment shown, but it is an important part in the Lemma 3.2. match returns no more than one method. full program. Lemma 3.3. match returns • iff it does not return a method. 3. THE VISITOR CALCULUS Lemma 3.4. find-method returns no more than one method. 3.1 Syntax and semantics Finally, we reach this theorem from Lemmas 3.1 and 3.4. To describe the computation, we formally define values: Theorem 3.5. For every ordered pair of classes from a set of classes, find-method returns exactly one method. 3.2 Type system 4. FROM VISITORS TO OBJECTS VisiCalc is “almost” untyped. We do not need types to We have demonstrated the connection between functions statically check invocations, for example, because the se- and visitors, specifically that a lambda term is translated mantics ensures an applicable method will always be found. into a new class and a creation of an instance of that class. We do need one thing to guarantee that a program will run We now link the visitor calculus with an object-oriented sys- correctly— every creation must have the appropriate num- tem. ber of arguments. Therefore VisiCalc does have a mini- mal type system, but it is enough to prove preservation and 4.1 Featherweight Java progress theorems. A typing environment is not a mapping, As our target language, we use Featherweight Java [6] but a single type, the “current” type in the type derivation without casts. We have removed casts because they are (or the environment can be empty, ∅). We use the metavari- not needed for the translation of the visitor calculus. A able B to range over types and the empty set, where as t Featherweight Java program is a list of classes and a main ranges over types only. Typing rules for VisiCalc: expression. The syntax and semantics of Featherweight Java without casts is in Figure 2. Note that this last semantics x ∈ fields(c, t) rule requires that the parameters to a function call be values. (14) c, t ` x This differs from the standard FJ definition, enforcing call- by-value semantics. The typing rules are as follows: Subtyping: c, B ` e1 c, B ` e2 (15) (30) c, B ` e1 e2 CD ` C <: C

c, t ` acceptor (16) CD ` C <: D CD ` D <: E (31) CD ` C <: E

c, B ` e | e |=| fields(c, t) | (17) CD(C) = class C extends D{. . .} c, B ` new t[e] (32) CD ` C <: D Expression Typing: c, t ` e 0 (18) c, t ` (t 7→ e) (33) CD; Z ` x ∈ Z(x) c, t ` m 0 (19) CD; Z ` η0 ∈ C0 fields-fj (CD, C0) = C f c ` t : t {x; m} (34) CD; Z ` η0.f ∈ Ci c, ∅ ` e ∀c ∈ c c, ∅ ` c (20) fields-fj (CD, C) = D f CD; Z ` η ∈ E CD ` E <: D c ` c e CD; Z ` new C(η) ∈ C Note that (17), if e is taken to be empty, implies that (35) c, ∅ ` new t[ ]. CD; Z ` η0 ∈ C0 mtype(CD, C0, m) = D → C Lemma 3.6. [Term substitution] If x = fields(c, t), c, t ` CD; Z ` η ∈ C CD ` C <: D (36) e, c, ∅ ` d, and c, ∅ ` f, then c, ∅ ` e{acceptor, b := f, d}. CD; Z ` η0.m(η) ∈ C Theorem 3.7. [Preservation] If c, ∅ ` e and (c, khei) → Method Typing: 0 0 (c, khe i) then c, ∅ ` e . CD; this : C, x : C ` η0 ∈ E0 Theorem 3.8. [Progress.] If e is closed and c, ∅ ` e, CD ` E0 <: C0 0 then either e is a value or there exists e such that (c, e) → CD(C) = class C extends D {. . .} 0 override(CD, D, m, C → C0) (c, e ). (37) CD ` C0 m(C x) {return η0; } OK in C Lemma 3.9. If (c, e) → (c, e0) and e is closed, then e0 is closed. Class Typing: CD ` µ OK in C fields-fj (CD, D) = D g We say that an expression e is stuck if it is not a value 0 0 K = C(D g, C f){ super (g); this.f = f; } and there is no expression e such that (c, e) → (c, e ). A (38) program goes wrong if it evaluates to a stuck expression. CD ` class C extends D{C f; Kµ} OK Program Typing: Theorem 3.10. Well-typed programs cannot go wrong. CD ` CD OK CD; ` η ∈ C (39) ` (CD, η) ∈ C P ::= (CD, η) programs η ::= expressions x variable references | η.f field references | new C(η) creations | η.m(η) method calls CD ::= class C extends C { C f; K M} classes K ::= C(C f) { super(f); this.f = f; } constructors µ ::= C m(C x) { return η; } methods v ::= new C(v) values

Auxiliary definitions useful for operational semantics: Field Lookup: (21) fields-fj (CD, Object) = ∅

CD(C) = class C extends D {C f; K µ} fields-fj (CD, D) = D g (22) fields-fj (CD, C) = D g, C f Method Type Lookup:

CD(C) = class C extends D {C f; K µ} B0 m(B x){ return η; } ∈ µ (23) mtype(CD, C, m) = B → B0

CD(C) = class C extends D{ C f; K µ} m not defined in µ (24) mtype(CD, C, m) = mtype(CD, D, m) Method Body Lookup:

CD(C) = class C extends D{ Cf; K µ} B0 m(Bx){ return η; } ∈ µ (25) mbody(CD, C, m) = (x, η)

CD(C) = class C extends D {C f; K µ} m not defined in µ (26) mbody(CD, C, m) = mbody(CD, D, m) Valid Method Overriding:

mtype(CD, D, m) = D → D0 implies C = D and C0 = D0 (27) override(CD, D, m, C → C0) Operational semantics:

fields-fj (CD, C) = Cf (28) (CD, Xhnew C(e).fii) 7→ (CD, Xheii)

mbody(CD, C, m) = (x, e ) 0 (29) (CD, Xhnew C(e).m(v)i) 7→ (CD,Xhe0{this, x := new C (e), v}i)

Figure 2: The syntax and semantics for Featherweight Java without casts.

4.2 Translation from visitors to objects At the simplest level, a field reference is translated to a We now present a translation from VisiCalc to Feather- field reference, a creation is translated into creation, etc. A class, moreover, is translated into a class. This implies a very weight Java, see Figure 3. We use (c, e) ¡ v to stand for the FJ classes and main expression that result from translating close correlation between the two languages and an isomor- phism between the two class hierarchies. Since VisiCalc has the VC program (c, e). e ¡ v stands for the translation of an no non-local field references, the translated program will not expression, and similarly (t 7→ e) ¡ v stands for the transla- c either, even though Featherweight Java allows them. Thus tion of a method. c ¡ v stands for the translation of a class in the context of a list of classes, and this is extended for all fields are translated as indirections from this in rule (40). Since all methods are translated into methods with the entire list of classes in the expected way. Finally, c ¡ vm creates a special method for a class; this is also extended for one argument, named acceptor, rule (43) translates param- a list of classes. eter references into parameter references, even though the Featherweight Java syntax does not distinguish them from

x ¡ v = this.x (40)

¡

new t[e] ¡ v = new t( e v) (41)

¡ ¡

(e1 e2) ¡ v = e2 v.accept( e1 v) (42)

acceptor ¡ v = acceptor (43)

¡ (t 7→ e) ¡ v = visitor visit t(t acceptor){return e v; } (44)

0 c 0

t : t {x; m} ¡ v = class t extends t { (45) visitor x; t (visitor x, fields(c, t0)) {super(y); this.x = x; }

visitor accept(visitor x) { return x.visit t(this); }

m ¡ v }

0 0

t : t {x; m} ¡ vm = visitor visit t(visitor acceptor){ return this.visit t (acceptor)} (46)

(c, e) ¡ v = class visitor extends Object { (47) visitor() { } visitor accept(visitor x){ return x.visit visitor(this); }

visitor visit visitor(Visitor acceptor) { return acceptor; }

c ¡ vm } c

c ¡ v

e ¡ v

Figure 3: Translation of the visitor calculus to Java

field references. class visitor extends Object { The most interesting part of this translation is how double visitor() {} dispatch is implemented since Featherweight Java does not visitor accept(visitor x) { support it. As is common in the visitor pattern, calling a return x.visit_visitor(this); visit method is actually a handshake requiring two method } calls, one call to the visit method and one to the accept visitor visit_visitor(visitor acceptor) { method. The accept method is necessary to select the cor- return acceptor; rect visit method in the visitor. Thus an invocation in Visi- } Calc is translated in rule (42) to the call of a method in the visitor visit_cla1(cla1 acceptor) { acceptor named accept. As we see in rule (45), each class return this.visit_visitor(acceptor); } has a method so named which is selected by Featherweight visitor visit_cla0(cla0 acceptor) { Java’s single dispatching and which calls an appropriate visit return this.visit_visitor(acceptor); } method, named for that class; such a method is translated } by rule (44). Featherweight Java’s single dispatching will class cla0 extends visitor { now select this method. visitor f; But what if no appropriate method exists in the visitor cla0 (visitor f){ super(); this.f = f; } class or any of its ancestors? The VisiCalc semantics then visitor accept(visitor x) { seek an appropriate visit method for the parent class of the return x.visit_cla0(this); acceptor. To implement this, the visitor class, which tops } the hierarchy, has a visit method for each class which will visitor visit_visitor(visitor acceptor) { be selected by single dispatch if no other method is found. return acceptor.accept(f); This method simply calls the visit method appropriate for } the acceptor’s parent class. This is shown in rules (46) and } (47). class cla1 extends visitor { Let us revisit the example from section 2, where we trans- cla1 (){ super(); } lated the apply function, λf.λx.fx, to the visitor calculus. visitor accept(visitor x) { If we translate that visitor program to Java using the above return x.visit_cla1(this); translation, we get: } visitor visit_visitor(visitor acceptor) { return new cla0(acceptor); } }

new cla1() Notice that visitor has visit methods for each of the [5] Erich Gamma, Richard Helm, Ralph Johnson, and two classes. Class cla0 represents λx.fx, and class cla1 John Vlissides. : Elements of Reusable represents λf.λx.fx. Class cla0 has a visit method to apply Object-Oriented Software. Addison-Wesley, 1995. its argument to its field, and class cla1 has a visit method [6] Atsushi Igarashi, Benjamin Pierce, and Philip Wadler. which constructs a new cla0. Featherweight Java: A minimal core calculus for Java and GJ. Technical Report MS-CIS-99-25, University of 4.3 Correctness Pennsylvania, 1999. First, we need some concept of translating typing environ- [7] Shriram Krishamurthi, Matthias Felleisen, and Dan ments in VisiCalc to typing environments in Featherweight Friedman. Synthesizing object-oriented and functional Java. An empty environment translates to an empty envi- design to promote re-use. In Proceedings of ECOOP’98, ronment, and an environment containing a class translates ninth European Conference on Object-Oriented Pro-

to a mapping of the fields in that class each to class visitor: gramming. Springer-Verlag (LNCS 1445), 1998.

[8] Gary T. Leavens and Todd D. Millstein. Multiple dis- ¡ ∅ ¡ = ∅ t = fields(t) : visitor

v v patch as dispatch on tuples. In OOPSLA ’98 Confer-

c ence Proceedings, pages 374–387, 1998.

¡ ¡ Lemma 4.1. If c, B ` e then c ¡ v B v ` e v : visitor.

[9] Todd Millstein and Craig Chambers. Modular statically

c typed multimethods. In Proceedings of the Thirteenth ¡ Lemma 4.2. If c ` c then c ¡ v ` C v OK. European Conference on Object-Oriented Programming We reach the following theorem from Lemmas 4.1 and 4.2 (ECOOP’99), volume 1628 of Lecture Notes in Com- puter Science, pages 279–303. Springer Verlag, 1999. Theorem 4.3. [Type Preservation.] If c ` (c, e) then

[10] Susumu Nishimura. Static typing for dynamic mes-

` (c, e) ¡ v OK. sages. In Proceedings of the 25 ACM Symposium on Principle of Programming Languages POPL, 1998. [11] Jens Palsberg and C. Barry Jay. The essence of the Our goal is to say that if a VisiCalc program execution visitor pattern. In Proceedings of COMPSAC’98, 22nd takes a step to a new state, then the encoding of that pro- Annual International Computer Software and Applica- gram into Featherweight Java takes a step or steps to be- tions Conference, pages 9–15, Vienna, Austria, August come the encoding of the resulting state on the VisiCalc 1998. side. [12] J. C. Reynolds. User-defined types and procedural data 0

Theorem 4.4. [Behavior Preservation.] If p → p as complementary approaches to data abstraction. In

∗ 0 S. A. Schuman, editor, New Directions in Algorithmic ¡ then p ¡ v → p v. Languages, IFIP Working Group 2.1 on Algol, INRIA. MIT Press, 1975. Reprinted in D. Gries, editor, Pro- 5. CONCLUSION gramming Methodology, Springer-Verlag, 1978, and in Our results provide a foundation for visitor-oriented pro- C. A. Gunter and J. C. Mitchell, editors, Theoretical gramming. Future work includes (1) proving the correctness Aspects of Object-Oriented Programming. of the translation from the λ-calculus to the visitor calculus, [13] Scala homepage. http://lamp.epfl.ch/scala. (2) developing more programming idioms for visitor-oriented [14] Standard ML of New Jersey. http://www.smlnj.org. programming, (3) developing a statically-typed version, re- [15] Philip Wadler. The expression problem. Circulated on lating it to a typed λ-calculus and Featherweight Java with a private mailing list, November 1998. casts, and (3) investigating a visitor calculus with symmetric double dispatching, including a type system for preventing message ambiguous errors, with inspiration from [1].

REFERENCES [1] Giuseppe Castagna, Giorgio Ghelli, and Giuseppe Longo. A calculus for overloaded functions with sub- typing. Information and Computation, 117(1):115–135, February 1995. [2] Curtis Clifton, Gary T. Leavens, Craig Chambers, and Todd Millstein. MultiJava: Modular open classes and symmetric for Java. In OOPSLA 2000 Conference on Object-Oriented Programming, Systems, Languages, and Applications, Minneapolis, Minnesota, pages 130–145, 2000. [3] William Cook. Object-oriented programming versus abstract data types. In Proceedings of REX Work- shop/School on the Foundations of Object-Oriented Languages. Springer-Verlag (LNCS 489), 1990. [4] Daniel P. Friedman, Mitchell Wand, and Christopher T. Haynes. Essentials of Programming Languages. The MIT Press and McGraw-Hill Book Company, 1992.