Browse the API
Git changelog
(this is GPL-ed software, distributed without any warranty)
What is it ?
In short
Object-oriented languages such as Java tend to structure the code
according to data more than according to functions, attaching the
functions (called "methods") to the objects.
Functional languages on the other side tend to structure the code
according to functions, using pattern matching to dispatch the
data.
Visitomatic allows you to structure some parts of your program in
the functional way, for when it makes a difference.
An example
For example, let's suppose we have binary trees whose nodes are the
"plus" operator and leaves are a "1".
The code of a treeSum function for summing the value
of those trees would actually be split in many classes when written
in Java, as it is in this example :
interface PlusOrJustInteger {
public int treeSum();
}
class JustInteger implements PlusOrJustInteger {
private int value = 1;
…
public int treeSum() {
return value;
}
}
class Plus implements PlusOrJustInteger {
private PlusOrJustInteger left;
private PlusOrJustInteger right;
…
public int treeSum() {
return left.treeSum() + right.treeSum();
}
}
This may, or may not, be the organisation that the user
wants : In some cases you want to have all of the
treeSum operation in one class.
The classic way of doing this in Object-Oriented programing
languages, is the so-called
visitor pattern
.
But in Java, this requires writing boilerplate code which
most of the time is not type-safe.
This project allows you to make the design pattern explicit, supressing the need for this tedious code. And it does that without relying on an external pre-processor, using Java annotations instead. Plus it is fast, comes with extensive JavaDoc, is thread-safe, type-safe and hand-made. Love it !
How to use it ?
I will show you how to use VisitOMatic on the
treeSum function presented above. I've chosen
this example because of its simplicity, but keep in mind
that VisitOMatic is more usefull when the visited objects
are complex.
Writing our tree
As we said before, an element of this tree is either an Integer
or a Plus node with two children. Here is a possible
definition for a such tree, please note that it does not specify
the implementation of treeSum.
/* An element of the tree */
abstract class PlusOrJustInteger {
}
/* Tree nodes */
class Plus extends PlusOrJustInteger {
private PlusOrJustInteger left;
private PlusOrJustInteger right;
}
/* Tree leaves */
class JustInteger extends PlusOrJustInteger
{
public Integer getValue() {
return 42;
}
}
Making it a Visitable
Now we want to mark what is interesting in this tree : in many objects, most fields are not interesting.
/* An element of the tree */
abstract class PlusOrJustInteger {
}
/* Tree nodes, marked as a Visitable */
class Plus extends PlusOrJustInteger implements Visitable {
@ToVisit // This annotation means that this field is usefull to visitors.
private PlusOrJustInteger left;
@ToVisit // Same here.
private PlusOrJustInteger right;
}
/* Tree leaves */
class JustInteger extends PlusOrJustInteger implements Visitable
{
@ToVisit // Here the return value is the usefull part
public Integer getValue() {
return 42;
}
}
At this point, our tree is done. We won't modify it when adding the sum operation, or any other operation.
Writing a Visitor, step by step
Now that the tree nodes are Visitable, we now can
write our sum and many other functions outside of the tree class.
We are helped in doing so by the availability of pattern matching.
Let's start with a SumVisitor class. This class
will implement the empty Visitor interface so as
to mark it as a visitor.
class SumVisitor implements Visitor {
}
It doesn't do much right now. Let's add some stuff. First we are
going to define what to do with objects of class
JustInteger. This method will be given the object and
its fields annotated with @ToVisit. An annotation
identifies it as a part of a visit.
class SumVisitor implements Visitor {
@VisitingMethod(visitName="sum")
private int sum(JustInteger it, int value) {
return value;
}
}
Let's do the same with the PlusOrJustInteger.
@VisitingMethod(visitName="sum")
private int sum(Plus it, PlusOrJustInteger left,
PlusOrJustInteger right) {
return sum(left)+sum(right);
}
So far so good ? We're quite done. All we need is to add the
sum (PlusOrJustInteger tree) method that will
call the others.
This require adding an VisitorRunner that is
responsible for dispatching the Visitable to
the right methods.
private static final VisitorRunner SUM_RUNNER = VisitorRunner.getInstance(SumVisitor.class, "sum");
And to invoke it when we are given a PlusOrJustInteger :
public Integer sum(PlusOrJustInteger it) throws VisitorRunnerException {
return SUM_RUNNER.visit(this, it);
}
And we're done !
Writing a Visitor, the final result
The final code for the Visitor is :
class SumVisitor implements Visitor {
/* The VisitorRunner responsible for dispatching the Visitables
* to the right methods.
*/
private static final VisitorRunner SUM_RUNNER =
VisitorRunner.getInstance(SumVisitor.class, "sum");
@VisitingMethod(visitName="sum")
private int sum(JustInteger it, int value) {
return value;
}
@VisitingMethod(visitName="sum")
private int sum(Plus it, PlusOrJustInteger left,
PlusOrJustInteger right) throws VisitorRunnerException {
return sum(left)+sum(right);
}
public Integer sum(PlusOrJustInteger it) throws VisitorRunnerException {
return SUM_RUNNER.visit(this, it);
}
}
This visitor is a stand-alone implementation of a sum. It can be
adapted to other classes and did not require modifying
the PlusOrJustInteger class.
This technique is usefull when you want to expose structured data
on which API-clients will perform operations, without modifying
those data.