The VISITOR Pattern As a Reusable, Generic, Type-Safe Component

The VISITOR Pattern As a Reusable, Generic, Type-Safe Component

The VISITOR Pattern as a Reusable, Generic, Type-Safe Component Bruno C. d. S. Oliveira, Meng Wang, and Jeremy Gibbons Oxford University Computing Laboratory Wolfson Building, Parks Road, Oxford OX1 3QD, UK {bruno,menw,jg}@comlab.ox.ac.uk Abstract the dominant decomposition’: when software can be mod- ularized along just one primary dimension at a time, con- The VISITOR design pattern shows how to separate the structure of an object hierarchy from the behaviour of traver- cerns that do not break down naturally along that dimension sals over that hierarchy. The pattern is very flexible; this very will be scattered across the dominant structure and entangled flexibility makes it difficult to capture the pattern formally. with other concerns. For another example, certain software We show how to capture the essence of the VISITOR designs seem to be hard to capture more abstractly as soft- pattern as a reusable software library, by using advanced ware components. This is the case for most of the ‘Gang of type system features appearing in modern object-oriented Four’ (GoF) design patterns [Gamma et al., 1995], which languages such as Scala. We preserve type-safety statically: cannot be expressed better than in terms of prose, pictures no reflection or similar mechanisms are used. The library is and prototypes. generic, in two senses: by types (the traversal return type and Our first contribution is to show that, with the modern ex- the object hierarchy shape) and by strategy (internal versus pressive type systems starting to appear in object-oriented external control, imperative versus functional behaviour, or- languages, we can in fact capture (at least the code aspects thogonal aspects such as tracing and memoisation). Finally, of) the VISITOR design pattern [Gamma et al., 1995] as a we propose a generalised datatype-like notation, providing a generic and type-safe visitor software component.Moreover, convenient functional decomposition style in object-oriented it is possible to capture a number of variations on the pattern languages. within one parametrizable component — specifically, we can support the following design decisions: who is responsi- 1. Introduction ble for traversing the object structure, the visitor or the object structure; whether the visitor is imperative (with results of A software component is a piece of software that can be traversals stored as mutable state in the visitor) or functional safely reused and flexibly adapted. Safety can be ensured, (pure, with results returned by the accept method); what or- for example, by a type system; flexibility stems from making thogonal concerns such as tracing or caching of computa- components parametrizable. Component-oriented program- tions are supported. Instead of committing to a particular ming [McIlroy, 1969], a programming style in which soft- decision at the time we design a visitor, as would be nec- ware is assembled from independent components, has for a essary with the informally-expressed VISITOR pattern, we long time been advocated as a solution to the so-called soft- can define a single visitor that postpones all of these design ware crisis [Naur and Randell, 1969]. decisions by allowing them to be specified by parametriza- This vision still has not been fully realised, largely due tion. to limitations of current programming languages. For ex- Our component is implemented in the Scala program- ample, regarding the structure of datatype definitions, most ming language [Odersky, 2006] and its type safety is stat- languages have a bias towards either object-oriented decom- ically guaranteed by the type system. The Scala features that position (where adding new variants is easy) or functional make this possible are parametrization by type (or gener- decomposition (where adding new functions is easy). This ics, as found in recent version of Java or C#) and abstract is an instance of what Tarr et al. [1999] call ‘the tyranny of types (although type-constructor polymorphism [Altherr and Cremet, 2007] could be used instead). As far as we are aware, all existing solutions trying to capture some notion of generic visitors [Palsberg and Jay, 1998, Visser, 2001, Grothoff, 2003, Forax et al., 2005, Meyer and Arnout, 2006] make use of reflection or introspection mechanisms that do not statically guarantee type-safety. Furthermore, most of [Copyright notice will appear here once ’preprint’ option is removed.] 1 those solutions only capture particular variations of the pat- Figure 2 shows examples of the two variations, using tern. functional-style VISITORs in Scala. In both visitors, the trait Our second contribution is a semantics for a generalised Tree and the classes Empty and Fork define a COMPOS- algebraic datatype notation . The notation allow us to define ITE. Using the visitor terminology, Tree is the element type parametric, mutually-recursive and existential visitors, be- and Empty and Fork are the concrete elements. The method ing comparable in expressive power to Haskell 98 and ML- accept, defined in Tree and implemented in the two concrete style datatypes. It also integrates well with object-oriented elements, takes a TreeVisitor object with two visit methods languages, allowing both datatypes and data-constructors to (one for each concrete element). Unlike with the traditional override or define new fields and methods. Furthermore, presentation of the VISITOR, the parameters of the construc- it generalises traditional algebraic datatypes, in the sense tors are fed directly into the visit methods instead of passing that both the traversal and the dispatching strategies are the whole constructed object. Parametrizing the visit meth- parametrizable. ods in this way gives a functional programming feel when using visitors. 2. The VISITOR as a Design Pattern Operations on trees are encapsulated in ConcreteVisitor objects. For example, an external visitor to compute the 2.1 TheVISITOR Pattern depth of a binary tree — explicitly propagating itself to The VISITOR design pattern is an alternative to the normal subtrees — is defined as follows: object-oriented approach to hierarchical structures, separat- object Depth extends TreeVisitor [int] { ing the operations from the object structure. Figure 1 shows def empty = 0 the class structure of the pattern. The Visitor interface de- def fork (x : int,l : Tree,r : Tree)= clares visit methods for each ConcreteElement type, imple- 1 +max (l.accept (this),r.accept (this)) mented in each ConcreteVisitor class; the Element abstract } superclass declares the accept method, taking a Visitor as ar- gument, defined in each ConcreteElement subclass to select Defining values of type Tree benefits from Scala’s case the appropriate visit method from a Visitor. class syntax, which avoids some uses of the new keyword. In contrast to the standard object-oriented decomposi- To use a ConcreteVisitor, we need to pass it as a parameter tion, the VISITOR pattern makes it easy to add new opera- to the accept method of a Tree value. As a simple example, tions — at the cost of making it difficult to add new vari- we define a method test to compute the depth of a small tree. ants. One can see the pattern as a way of simulating double val atree = Fork (3,Fork (4,Empty,Empty),Empty) dispatch in a single-dispatch language: the method imple- def test = atree.accept (Depth) mentation chosen depends on the dynamic types of both the ConcreteElement and the ConcreteVisitor. 2.2 Imperative and Functional VISITORs 2.4 The Class Explosion In the traditional presentation of the visitor pattern, the visit As is the case with most design patterns, the VISITOR pat- and accept methods return no result; any value computed by tern presents the programmer with a number of design deci- the visitor is stored in the visitor for later retrieval. An alter- sions. An obvious dimension of variation follows the shape native is for the visit and accept methods to return the value of the object structure being traversed: the Visitor interface directly. Buchlovsky and Thielecke [2005] use the term im- for binary trees will differ from that for lists. We have just perative visitor for one that has visit and accept methods that discussed two other dimensions of choice: imperative versus return void, with all computations executed through side- functional behaviour, and internal versus external control. effects, accumulating results via mutable state; in contrast, A fourth dimension captures certain cross-cutting concerns, a functional visitor is immutable, all computations yielding such as tracing of execution and memoization of results. their results through the return values of the visit and accept Handled naively, this flexibility introduces some prob- methods, which are pure. lems. For one thing, capturing each combination sepa- rately leads to an explosion in the number of classes: 2.3 Internal and External VISITORs ImpExtTreeBasicVisitor for imperative external tree visi- Gamma et al. [1995] raise the question of where to place tors, FuncIntTraceListVisitor for functional internal tracing the traversal code: in the object structure itself (in the accept list visitors, and so on. Secondly, the dependency on user- methods), or in the concrete visitors (in the visit methods). supplied information (the shape of the object structure) pre- Buchlovsky and Thielecke [2005] use the term internal visi- vents these classes from being provided in a library. Finally, tor for the former approach, and external visitor for the lat- because the variations have different interfaces, the choice ter. Internal

View Full Text

Details

  • File Type
    pdf
  • Upload Time
    -
  • Content Languages
    English
  • Upload User
    Anonymous/Not logged-in
  • File Pages
    17 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