yf0994 / guava-libraries

Automatically exported from code.google.com/p/guava-libraries
Apache License 2.0
0 stars 0 forks source link

Either<L,R> class - kind of extension for Optional<T> #1149

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
Based on the one from Scala 
http://www.scala-lang.org/api/current/scala/Either.html

Original issue reported on code.google.com by alex.pan...@gmail.com on 14 Sep 2012 at 8:33

GoogleCodeExporter commented 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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
+ 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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago

Original comment by cgdecker@google.com on 1 Nov 2014 at 4:18

GoogleCodeExporter commented 9 years ago

Original comment by cgdecker@google.com on 3 Nov 2014 at 9:08