<< Visitor Pattern | Visitor Variations >>
This page explains how to use ADTs, but it doesn't cover exactly what they are or why to use them.
If you have an ADT definition in jADT's syntax for a binary tree
package com.pogofish.jadt.samples.whathow.data IntBinaryTree = Node(int value, IntBinaryTree left, IntBinaryTree right) | EmptyTree
Using ant or the shell jADT generates Java code such that with a couple of imports
import com.pogofish.jadt.samples.whathow.data.IntBinaryTree; import com.pogofish.jadt.samples.whathow.data.IntBinaryTree.*; import static com.pogofish.jadt.samples.whathow.data.IntBinaryTree.*;
You can create a new Tree with code like
IntBinaryTree tree = _Node(42, _Node(12, _EmptyTree(), _EmptyTree()), _Node(103, _EmptyTree(), _Node(110, _EmptyTree(), _EmptyTree())));
jADT also allows ADTs to be generic. The IntBinaryTree above could be replaced with a more generic version
BinaryTree<T> = Node(T value, BinaryTree<T> left, BinaryTree<T> right) | EmptyTree
With that you can create a String BinaryTree with code like
BinaryTree<String> empty = BinaryTree.<String>_EmptyTree(); return _Node("hello", _Node("goodbye", empty, empty), _Node("whatever", empty, _Node("foo", empty, empty)));
You can also achieve the same thing with 'new BinaryTree.Node<String>("hello"...)' and 'new BinaryTree.EmptyTree<String>()' but that
In Java you can use "switch" to decide what to do you based on specific values of primitives and Enums. If the primitive happens to be a boolean then you can also use "if" or the ternary operation (x ? y : z). But what about ADTs? How do you decide what to do based on a specific ADT constructor?
Most statically typed functional programming languages have a pattern matching for making decisions based on ADTs. Java doesn't. So with jADT you have a couple of choices, you can use instanceof and casting or preferably you can use the visitor interfaces that jADT generates.
Here's using a MatchBlock style Visitor to find the max of an IntBinaryTree where null indicates an empty tree.
public Integer max(IntBinaryTree tree) { return tree.match(new IntBinaryTree.MatchBlock<Integer>() { @Override public Integer _case(Node x) { final Integer maxLeft = max(x.left); final int l = maxLeft == null ? Integer.MIN_VALUE : maxLeft; final Integer maxRight = max(x.right); final int r = maxRight == null ? Integer.MIN_VALUE : maxRight; return Math.max(Math.max(l, r), x.value); } @Override public Integer _case(EmptyTree x) { return null; } }); }
Which says that the max of an EmptyTree is null while the max of a Node is the max of the node's value compared with the max of the left tree compared with the max of the right tree.
Languages with pattern matching also generally allow a "don't care" case, a default for when no other case matches. jADT emulates that using a small variation of the visitor pattern.
Given the ADT
TPSReportStatus= Pending | Approved(final Manager approver) | Denied(final Manager rejector)
You can see if an TPS report was approved by
public boolean isApproved(TPSReportStatus status) { return status.match(new TPSReportStatus.MatchBlockWithDefault<Boolean>() { @Override public Boolean _case(Approved x) { return true; } @Override protected Boolean _default(TPSReportStatus x) { return false; } }); }
So for Approved the result is true, but for all other statuses (Pending and Denied) the result is false.
In this particular case the same thing could be achieved with instanceof pretty nicely
public boolean isApprovedV2(TPSReportStatus status) { return status instanceof TPSReportStatus.Approved; }
A few rules of thumb for when to use instanceof vs a Visitor.
Besides the Visitor and VisitorWithDefault variations seen on this page, jADT also supports variations that return void.