Is there any way to define a sum type in Java? Java seems to naturally support product types directly, and I thought enums might allow it to support sum types, and inheritance looks like maybe it could do it, but there is at least one case I can't resolve. To elaborate, a sum type is a type which can have exactly one of a set of different types, like a tagged union in C. In my case, I'm trying to implement haskell's Either type in Java:
data Either a b = Left a | Right b
but at the base level I'm having to implement it as a product type, and just ignore one of its fields:
public class Either<L,R>
{
private L left = null;
private R right = null;
public static <L,R> Either<L,R> right(R right)
{
return new Either<>(null, right);
}
public static <L,R> Either<L,R> left(L left)
{
return new Either<>(left, null);
}
private Either(L left, R right) throws IllegalArgumentException
{
this.left = left;
this.right = right;
if (left != null && right != null)
{
throw new IllegalArgumentException("An Either cannot be created with two values");
}
if (left == right)
{
throw new IllegalArgumentException("An Either cannot be created without a value");
}
}
.
.
.
}
I tried implementing this with inheritance, but I have to use a wildcard type parameter, or equivalent, which Java generics won't allow:
public class Left<L> extends Either<L,?>
I haven't used Java's Enums much, but while they seem the next best candidate, I'm not hopeful.
At this point, I think this might only be possible by type-casting Object
values, which I would hope to avoid entirely, unless there's a way to do it once, safely, and be able to use that for all sum types.
Inheritance can be used to emulate sum types (Disjoint unions), but there are a few issues you need to deal with:
Optional.get()
. Ideally, this method would only be available on a disjoint type who's value is known to besome
rather thannone
. But there's no way to do that, so it's an instance member of a generalOptional
type. It throwsNoSuchElementException
if you call it on an optional whose "case" is "none".TL;DR: Functional programming in Java is not a pleasant experience.
A standard way of encoding sum types is Boehm–Berarducci encoding (often referred to by the name of its cousin, Church encoding) which represents an algebraic data type as its eliminator, i.e., a function that does pattern-matching. In Haskell:
In Java this would look like a visitor:
Example usage:
For convenience, you can make a factory for creating
Left
andRight
values without having to mention the type parameters each time; you can also add a version ofmatch
that acceptsConsumer<A> left, Consumer<B> right
instead ofFunction<A, R> left, Function<B, R> right
if you want the option of pattern-matching without producing a result.Make
Either
an abstract class with no fields and only one constructor (private, no-args, empty) and nest your "data constructors" (left
andright
static factory methods) inside the class so that they can see the private constructor but nothing else can, effectively sealing the type.Use an abstract method
either
to simulate exhaustive pattern matching, overriding appropriately in the concrete types returned by the static factory methods. Implement convenience methods (likefromLeft
,fromRight
,bimap
,first
,second
) in terms ofeither
.Pleasant and safe! No way to screw it up. Because the type is effectively sealed, you can rest assured that there will only ever be two cases, and every operation ultimately must be defined in terms of the
either
method, which forces the caller to handle both of those cases.Regarding the problem you had trying to do
class Left<L> extends Either<L,?>
, consider the signature<A, B> Either<A, B> left(A value)
. The type parameterB
doesn't appear in the parameter list. So, given a value of some typeA
, you can get anEither<A, B>
for any typeB
.Alright, so the inheritance solution is definitely the most promising. The thing we would like to do is
class Left<L> extends Either<L, ?>
, which we unfortunately cannot do because of Java's generic rules. However, if we make the concessions that the type ofLeft
orRight
must encode the "alternate" possibility, we can do this.Now, we would like to be able to convert
Left<Integer, A>
toLeft<Integer, B>
, since it doesn't actually use that second type parameter. We can define a method to do this conversion internally, thus encoding that freedom into the type system.Complete example:
Of course, you'll want to add some functions for actually accessing the contents, and for checking whether a value is
Left
orRight
so you don't have to sprinkleinstanceof
and explicit casts everywhere, but this should be enough to get started, at the very least.