next up previous
Up: 1.6 The Visitor Pattern Previous: 1.6.3 Polymorphic Visitors

1.6.4 Polymorphic Visitors with Arguments

To make our visitor example more interesting and realistic, let us include variables in the type ArithExpr.

class Var extends ArithExpr {
  String name;
  Var(String n) {
    name = n;
  }
  public String toString() {
    return name;
  }
  Object accept(Visitor v) {
    return v.forVar(this);
  }
}
Then the abstract visitor class for ArithExprs has the form:

abstract class Visitor {
  abstract Object forConst(Const c);
  abstract Object forSum(Sum c);
  abstract Object forProd(Prod p);
  abstract Object forVar(Var v);
}

There are no fields to initialize in the abstract visitor, so the default constructor provided by the Java compiler suffices.

The concrete visitor class that implements expression evaluation is:

class EvalVisitor extends Visitor {
  Env env;  // the parameter: an environment for looking up variables
  EvalVisitor(Env e) {
    env = e;
  }
  Object forConst(Const c) {
    return c;  // don't need to do anything special for constants
  }
  Object forSum(Sum s) {
    return new Const( ((Const)s.e1.accept(this)).value +
                      ((Const)s.e2.accept(this)).value );
  }
  Object forProd(Prod p) {
    return new Const( ((Const)p.e1.accept(this)).value) *
                      ((Const)p.e2.accept(this)).value );
  }
  Object forVar(Var v) {
    return env.lookup(v.name);
  }
}
The environment env, which was an explicit parameter of the eval method in our method-based implementation for evaluation, is now a field of the visitor. As before, it is directly used only for evaluating instances of Var, but now we don't need to explicitly pass the environment through method argument lists.

Since we are programming in a functional style, the forConst method need only return its argument as the result, rather than allocating a copy. The forSum and forProd methods are mostly straightforward, evaluating the subexpressions first and combining the results. The only subtlety is that since accept now returns an instance of Object rather than an int, we need to perform an explicit type cast to get the values for the left and right subexpressions. For example, to obtain the value for the left subexpression in a Sum, we have

     ((Const)s.e1.accept(this)).value

The expression s.e1.accept(this) computes a Const whose value field is the value of the expression s.e1. But the declared return type for accept is Object, and since an Object has no field named value, we cannot extract the value directly. Since we know that the Object is in fact a Const, we can insert an explicit type cast to Const, and then extract the value.

The forVar method looks up the value of the variable in the current environment. The environment is passed in when an EvalVisitor is created, and is presumably given bindings for the existing variables beforehand.

Exercise Finish the visitor-based implementation of expression evaluatoin, including the definition of the Environment class, by yourself. Can you think of other operations on expressions that the visitor pattern might help you implement?


next up previous
Up: 1.6 The Visitor Pattern Previous: 1.6.3 Polymorphic Visitors
Robert Cartwright, Spring 1999