Closed GoogleCodeExporter closed 9 years ago
Implementation:
public abstract class Either<LEFT, RIGHT> {
public static <LEFT, RIGHT> Either<LEFT, RIGHT> left(LEFT value) {
return new Left<LEFT, RIGHT>(checkNotNull(value));
}
public static <LEFT, RIGHT> Either<LEFT, RIGHT> right(RIGHT value) {
return new Right<LEFT, RIGHT>(checkNotNull(value));
}
// accessed from enclosed class
Either() {
}
public abstract boolean isLeft();
public abstract boolean isRight();
public abstract LEFT left();
public abstract RIGHT right();
@Override
public abstract boolean equals(Object object);
/**
* Returns a hash code for this instance.
*/
@Override
public abstract int hashCode();
/**
* Returns a string representation for this instance. The form of this string representation is unspecified.
*/
@Override
public abstract String toString();
private static class Left<LEFT, RIGHT> extends Either<LEFT, RIGHT> {
final LEFT value;
public Left(LEFT value) {
this.value = value;
}
@Override
public boolean isLeft() {
return true;
}
@Override
public boolean isRight() {
return false;
}
@Override
public LEFT left() {
return value;
}
@Override
public RIGHT right() {
throw new IllegalStateException("No right");
}
@Override
public boolean equals(Object object) {
if (object instanceof Left<?, ?>) {
final Left<?, ?> other = (Left<?, ?>) object;
return value.equals(other.value);
}
return false;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public String toString() {
return "Either.left(" + value + ")";
}
}
private static class Right<LEFT, RIGHT> extends Either<LEFT, RIGHT> {
final RIGHT value;
public Right(RIGHT value) {
this.value = value;
}
@Override
public boolean isLeft() {
return false;
}
@Override
public boolean isRight() {
return true;
}
@Override
public LEFT left() {
throw new IllegalStateException("No left");
}
@Override
public RIGHT right() {
return value;
}
@Override
public boolean equals(Object object) {
if (object instanceof Right<?, ?>) {
final Right<?, ?> other = (Right<?, ?>) object;
return value.equals(other.value);
}
return false;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public String toString() {
return "Either.right(" + value + ")";
}
}
}
Original comment by alex.pan...@gmail.com
on 14 Sep 2012 at 8:34
This has already been discussed somewhat:
https://groups.google.com/d/msg/guava-discuss/rsVLDmTtrYM/gdAr00gkpRYJ
Original comment by wasserman.louis
on 14 Sep 2012 at 3:05
Can you give us an example use case, and a demonstration of what the code would
look like with and without this feature?
Original comment by lowas...@google.com
on 12 Oct 2012 at 4:36
Most of the time I use it to return a success value or some error status when
exceptions are not appropriate.
There are different ways to work without this feature:
a) return Object and use instanceof checks
b) return Object[2]
c) define 3 classes like here with more specific names
From one point of view it is similar to Optional, but absent() variant also has
associated value. From the other point of view, it could be rejected with the
same argumentation as Pair<A,B> :-)
Original comment by alex.pan...@gmail.com
on 12 Oct 2012 at 6:00
Note: Either<A,B> seems to come up in this "result or failure" context way more
often than anything else. One possibility is that we should address that
specific use case (which somewhat resembles Future, but without the completion
state or waiting thereon). Or it's possible that "Either" per se really is more
generally useful, I'm just not really seeing it yet.
Original comment by kevinb@google.com
on 9 Jan 2013 at 2:08
This guy needs an Either<Exception, Option<T>> (he just doesn't know it):
http://stackoverflow.com/questions/14387434/how-to-handle-exception-or-null-case
s-uniformally-in-java
Original comment by jem.maw...@gmail.com
on 17 Jan 2013 at 8:29
I don't think this can be rejected with the same argumentation as Pair<A,B>
(although I am not too familiar with the argumentation).
Either<Left, Right> has a precise use-case. It is an extended Optional, where
the error condition can contain some information.
It is used to convey exactly the same information as an exception would do, but
it is meant to force the the immediate caller to handle the issue, unlike an
exception.
Note that the names 'left' and 'right' are important, they should not be A and
B. There is *one* right/correct value, and the other is left/error.
Wrt use-cases, my use-case looks something like this:
public interface MyFooParser {
enum PacketError { WRONG_PROTOCOL, TOO_SHORT, ALREADY_RECEIVED };
Either<PacketError, Result> parsePacket(ByteBuffer packet);
}
But my main point is this: What makes Either useful in functional programming
is a combination of the error-handling, monads (let's ignore that), but also
*pattern matching*.
So the last point is that a "correct" Either should force the user to use
pattern matching when examining the result.
So if Left and Right were subclasses of Either, and the inheritance hierarchy
closed (not open), then
http://stackoverflow.com/questions/5579309/switch-instanceof
switch (instanceof foo.parsePacket(packet)) {
case Left: ...
case Right: ...
}
would be close.
But that isn't possible, so another variant would be to make Either somehow
hold an enum, like so:
Either<PacketError, Result> res = foo.parsePacket(packet);
switch (res.get()) {
case LEFT : ...(..., res.left(), ...);
case RIGHT: ...(..., res.right(), ...);
}
but that is also less than ideal because the enums cannot hold a value, so we
cannot protect against misuse of res.left() and res.right().
What we want is something more like this:
switch (foo.parsePacket(packet).get()) {
case LEFT x: ...(..., x, ...);
case RIGHT x: ...(..., x, ...);
}
I don't have a solution to this, except not implementing Either until we can
use lambda.
All the solutions on stackoverflow are less than ideal, because making the user
actually call res.isLeft() or res.isRight() is fragile.
With lambda, we can do this:
foo.parsePacket(packet).fold(
left -> error(left), // left is of type PacketError
right -> doSomeMoreProcessing.callBar(..., right, ...).fold(
left -> someError(left),
right -> doEvenMoreProcessing...
...
where fold is a function that takes two lambdas, one invoked if it is Left, the
other invoked if it is Right.
The above can be done more concise by using the monad bind function:
static <L,RIN, ROUT> Either<L, ROUT> transform(Either<L, RIN> value, {RIN =>
Either<L, ROUT>} func) {
value.fold(
left -> return left,
right -> return func.invoke(right));
// or if (value.isLeft()) {
// return value.left;
// } else {
// return func.invoke(value.right);
// }
}
so we then write, assuming we need to do a chain of operations on something
class MyFoo {
enum Error { WRONG_PROTOCOL, TOO_SHORT, ALREADY_RECEIVED };
Either<Error, Result1> parsePacket(ByteBuffer packet);
Either<Error, Result2> processResult1(Result1 arg);
Either<Error, Result3> processResult2(Result2 arg);
Either<Error, Result> processResult3(Result3 arg);
}
Either<Error, Result> res = transform(transform(transform(
foo.parsePacket(packet),
step1 -> foo.processResult1(step1)),
step2 -> foo.processResult2(step2)),
step3 -> foo.processResult3(step3));
since Optional already has a transform method, this is equivalent to:
Optional<Result> res = transform(transform(transform(
foo.parsePacket(packet),
step1 -> foo.processResult1(step1)),
step2 -> foo.processResult2(step2)),
step3 -> foo.processResult3(step3));
but where the 'foo' interface only supports a crude error reporting.
Notice how the intermediate types do not have to match, as 'transform' composes
the functions.
Some utility functions like transform2, transform3, transforms where the
intermediate type is the same etc. could be created. Bonus points if
'transform' could be made into an interface (called Monad) so that generic
utilities can be written.
Original comment by alexande...@gmail.com
on 30 Jan 2013 at 3:53
Implementations of Either have appeared in three projects in Google. One copy
is unused. The others are used once each, both to return either of two
different types of successful results, rather than either an error or a
success. The one is returning either a Widget to be inserted or a text string
to be inserted with setInnerText. (At a glance, I'd say that it would be better
off making the setWidget/setText call itself, rather than returning the Either
instance, but maybe there's something more going on. Another option is to
return a Widget in either case. There are performance implications, but if the
Widget is acceptable in half of cases, it's likely acceptable in the other.)
The other user is returning an Either<Pair<String, String>, String> with a
15-line Javadoc comment explaining the 3 possible result variants.
An Either class that supports only a value/exception choice is still something
to consider. Without pattern matching, though, it has a hard time competing
with plain exceptions. Maybe things will change with JDK8.
Original comment by cpov...@google.com
on 30 Jan 2013 at 3:54
I'm in a similar bind (no pun intended) with dns, particularly route53.
a record there can either have a target of a host list, or an alias to another
record in a different zone. subclassing is possible, but would be annoying
imho. Any ideas?
Original comment by fernc...@gmail.com
on 31 Jan 2013 at 12:16
currently sidestepping this by javadoc instructions.
/**
* Type-specific data that differentiates the RRs in this set. Empty if
* {@link #getType()} is {@link Type#A} or {@link Type#AAAA} and
* {@link Type#getPointer} is present.
*/
public Set<String> getData() {
return data;
}
/**
* When present, {@link #getType()} is {@link Type#A} or {@link Type#AAAA}.
* Instead of {@link #getData()} containing the corresponding IP addresses,
* the server will follow this link and resolve one on-demand.
*/
public Optional<Pointer> getPointer() {
return pointer;
}
Original comment by fernc...@gmail.com
on 31 Jan 2013 at 1:40
For those who can't wait:
https://bitbucket.org/atlassian/fugue/src
Original comment by harshad...@gmail.com
on 20 Sep 2013 at 7:23
+ 1 to #11. Thanks Atlassian ! :)
By the way it works WITH Guava, not replace it, right?
Do you have foldLeft() and friends too?
Original comment by ceefour666@gmail.com
on 20 Sep 2013 at 7:56
Bravo, Atlassian! It looks like Guava is in crisis now. Nothing really new was
added since 10.0.
Original comment by orionllm...@gmail.com
on 3 Oct 2013 at 3:16
This issue has been migrated to GitHub.
It can be found at https://github.com/google/guava/issues/<id>
Original comment by cgdecker@google.com
on 1 Nov 2014 at 4:13
Original comment by cgdecker@google.com
on 1 Nov 2014 at 4:18
Original comment by cgdecker@google.com
on 3 Nov 2014 at 9:08
Original issue reported on code.google.com by
alex.pan...@gmail.com
on 14 Sep 2012 at 8:33