5.92

5 2/24: Java, Arithmetic and Visitors

Warmup: simple data, different language

In this lab, we’ll be introducing you to designing interfaces, abstract and concrete classes, and generic classes in Java. If you follow the design recipe, the process is mostly the same, though you may have to think about types and Java syntax.

Exercise 1. Design an abstract class AExp to represent arithmetic expressions, and three concrete classes AConst, APlus and ATimes representing integer constants, addition of two summands and multiplication of two factors, respectively.

Exercise 2. Design a method sameExp that computes when two expressions are the same. Use double-dispatch.

Exercise 3. Design a method eval that evaluates the arithmetic expression to an answer (an int). AConst expressions evaluate to their numeric values; APlus expressions evaluate their left and right sides and add the results; and ATimes expressions multiply the result of evaluating their left and right sides.

Exercise 4. Design a method print that produces a String representing the fully-parenthesized expression, as if you were writing it down by hand. For example, you should produce "(1 + ((2 * (3 + 4)) * 5))", and all of the parentheses are needed.

Visiting rights Did you notice a similar structure in the code of the eval and print methods? Each of these methods returns some value of a given type (int for eval, and String for print) and recursively combines the results in a unique way for each subclass of AExp. In a moment, we’re going to define another operation on arithmetic expressions — wouldn’t it be nice if we could define that common pattern once and for all?

Exercise 5. Define a generic IAExpVisitor interface: it should take a single type parameter that represents the “answer type” of the computation, and should have one method for each subclass of AExp:

interface IAExpVisitor<T> {

  T visitAConst(AConst exp);

  T visitAPlus(APlus exp);

  T visitATimes(ATimes exp);

}

Next, define a method visit (IAExpVisitor visitor) on the AExp class, and implement it in each subclass. (Hint: what should its return type be?) Each class’s method should call the appropriately named method on the visitor, passing itself in as the argument. (Do you see how this is related to the double-dispatch pattern?)

Rewrite your answers to the previous exercises as visitors. So for example, you should be able to write

AExp onePlusTwoTimesThree =

    new APlus(new AConst(1), new ATimes(new AConst(2), new AConst(3)));

t.checkExpect(onePlusTwoTimesThree.visit(new EvalVisitor()),

              7);

t.checkExpect(onePlusTwoTimesThree.visit(new PrintVisitor()),

              "(1 + (2 * 3))");

If everything works, you should be able to delete the eval and print methods, since they’ve been replaced by the new visitors.

Exercise 6. Define a new operation on expressions that “mirrors” them, such that the mirror of (1 + ((2 * 3) + 4)) is ((4 + (3 * 2)) + 1). Is it easier or harder to define this as a method or as a new visitor? (Which involves changing fewer classes?)

Exercise 7. Define a new class ASub that represents subtraction. Extend your answers to parts 2, 5 and 6 to support the new ASub class.

Recap

Exercise 8. In this lab, you’ve extended a class hierarchy with new operations (via visitors) and new data types (via subclasses). In one paragraph, summarize which is “easier” to do, and which one is more “invasive” in changing lots of parts of code. Please email this paragraph to me and Chris before Thursday’s lecture.