The visitor pattern doesn't require you to have a hierarchy of operations. It doesn't even require the sum type options to form a public type hierarchy. For example, the following Java code implements the typical binary-tree sum type:
public abstract class Tree<V>
{
public abstract <R> R apply(Visitor<? super V, R> visitor);
public interface Visitor<V, R>
{
public R whenLeaf(V value);
public R whenNode(Tree<? extends V> left, Tree<? extends V> right);
}
public static <V> Tree<V> leaf(V value)
{
return new Tree<V>()
{
@Override
public <R> R apply(Visitor<? super V, R> visitor)
{
return visitor.whenLeaf(value);
}
};
}
public static <V> Tree<V> node(Tree<? extends V> left, Tree<? extends V> right)
{
return new Tree<V>()
{
@Override
public <R> R apply(Visitor<? super V, R> visitor)
{
return visitor.whenNode(left, right);
}
};
}
private Tree() { }
}
This is what I'm describing.(Incidentally, that design also allows you to replace the implementation by a tagged-union approach. The result wouldn't be the full visitor pattern anymore, of course.)
EDIT: Another way to put this: The visitor pattern abstracts away the case-distinction mechanism into a single implementation. In your code example, every operation (like sumTree) has to re-implement the case-distinction mechanism, that is, the ifs and the type checks. The visitor pattern decouples the case-distinction mechanism from the concrete case distinctions.