How can I simulate Haskell's “Either a b” in Java

后端 未结 14 2200
梦毁少年i
梦毁少年i 2020-12-07 22:40

How can I write a typesafe Java method that returns either something of class a or something of class b? For example:

public ... either(boolean b) {
  if (b)         


        
相关标签:
14条回答
  • 2020-12-07 23:17

    I've implemented it in a Scala-like fashion in the following way. It's a little verbose (it is Java, after all :)) but it's type safe.

    public interface Choice {    
      public enum Type {
         LEFT, RIGHT
      }
    
      public Type getType();
    
      interface Get<T> {
         T value();
      }
    }
    
    public abstract class Either<A, B> implements Choice {
    
      private static class Base<A, B> extends Either<A, B> {
        @Override
        public Left leftValue() {
          throw new UnsupportedOperationException();
        }
    
        @Override
        public Right rightValue() {
          throw new UnsupportedOperationException();
        }
    
        @Override
        public Type getType() {
          throw new UnsupportedOperationException();
        }
      }
    
      public abstract Left leftValue();
    
      public abstract Right rightValue();
    
      public static <A, B> Either<A, B> left(A value) {
        return new Base<A, B>().new Left(value);
      }
    
      public static <A, B> Either<A, B> right(B value) {
        return new Base<A, B>().new Right(value);
      }
    
      public class Left extends Either<A, B> implements Get<A> {
    
        private A value;
    
        public Left(A value) {
          this.value = value;
        }
    
        @Override
        public Type getType() {
          return Type.LEFT;
        }
    
        @Override
        public Left leftValue() {
          return Left.this;
        }
    
        @Override
        public Right rightValue() {
          return null;
        }
    
        @Override
        public A value() {
          return value;    
        }
      }
    
      public class Right extends Either<A, B> implements Get<B> {
    
        private B value;
    
        public Right(B value) {
          this.value = value;
        }
    
        @Override
        public Left leftValue() {
          return null;
        }
    
        @Override
        public Right rightValue() {
          return this;
        }
    
        @Override
        public Type getType() {
          return Type.RIGHT;
        }
    
        @Override
        public B value() {
          return value;
        }
      }
    }
    

    Then you can pass Either<A,B> instances around on your code. The Type enum is mainly used on switch statements.

    Creating Either values is simple as:

    Either<A, B> underTest;
    
    A value = new A();
    
    underTest = Either.left(value);
    
    assertEquals(Choice.Type.LEFT, underTest.getType());
    assertSame(underTest, underTest.leftValue());
    assertNull(underTest.rightValue());
    assertSame(value, underTest.leftValue().value());
    

    Or, in the typical situation where it is used instead of exceptions,

    public <Error, Result> Either<Error,Result> doSomething() {
        // pseudo code
        if (ok) {
            Result value = ...
            return Either.right(value);
        } else {
            Error errorMsg = ...
            return Either.left(errorMsg);
        }
    }
    
    // somewhere in the code...
    
    Either<Err, Res> result = doSomething();
    switch(result.getType()) {
       case Choice.Type.LEFT:
          // Handle error
          Err errorValue = result.leftValue().value();
          break;
       case Choice.Type.RIGHT:
          // Process result
          Res resultValue = result.rightValue().value();
          break;
    }
    

    Hope it helps.

    0 讨论(0)
  • 2020-12-07 23:23

    Change your design so that you don't need this rather absurd feature. Anything you'd do with the return value would require some sort of if/else construct. It would just be very, very ugly.

    From a quick Googling, it seems to me that the only thing Haskell's Either is commonly used for is error reporting anyway, so it looks like exceptions are actually to correct replacement.

    0 讨论(0)
  • 2020-12-07 23:25

    From http://blog.tmorris.net/posts/maybe-in-java/ I learned that you can make the outer class's constructor private so only nested classes can subclass it. This trick is just as type safe as the best above, but much less verbose, works for any ADT you want like Scala's case class.

    public abstract class Either<A, B> {
        private Either() { } // makes this a safe ADT
        public abstract boolean isRight();
        public final static class Left<L, R> extends Either<L, R>  {
            public final L left_value;
            public Left(L l) { left_value = l; }
            public boolean isRight() { return false; }
        }
        public final static class Right<L, R> extends Either<L, R>  {
            public final R right_value;
            public Right(R r) { right_value = r; }
            public boolean isRight() { return true; }
        }
    }
    

    (started from top answer's code and style)

    Note that:

    • The finals on the subclass are optional. Without them you can subtype Left and Right, but still not Either directly. Thus without the finals Either has limited width but unbounded depth.

    • With ADTs like this, I see no reason to jump on the whole anti-instanceof bandwagon. A boolean works for Maybe or Either, but in general instanceof is your best and only option.

    0 讨论(0)
  • 2020-12-07 23:28

    There is a stand-alone implementation of Either for Java 8 in a small library, "ambivalence": http://github.com/poetix/ambivalence

    It is closest to the Scala standard implementation - for example, it provides left and right projections for map and hashMap operations.

    There is no direct access to the left or right values; rather, you join the two types by providing lambdas to map them into a single result type:

    Either<String, Integer> either1 = Either.ofLeft("foo");
    Either<String, Integer> either2 = Either.ofRight(23);
    String result1 = either1.join(String::toUpperCase, Object::toString);
    String result2 = either2.join(String::toUpperCase, Object::toString);
    

    You can get it from Maven central:

    <dependency>
        <groupId>com.codepoetics</groupId>
        <artifactId>ambivalence</artifactId>
        <version>0.2</version>
    </dependency>
    
    0 讨论(0)
  • 2020-12-07 23:29

    Since you've tagged Scala, I'll give a Scala answer. Just use the existing Either class. Here's an example usage:

    def whatIsIt(flag: Boolean): Either[Int,String] = 
      if(flag) Left(123) else Right("hello")
    
    //and then later on...
    
    val x = whatIsIt(true)
    x match {
      case Left(i) => println("It was an int: " + i)
      case Right(s) => println("It was a string: " + s)
    }
    

    This is completely type-safe; you won't have problems with erasure or anything like that... And if you simply can't use Scala, at least use this as an example of how you can implement your own Either class.

    0 讨论(0)
  • 2020-12-07 23:30

    You can have a close correspondence with Haskell by writing a generic class Either, parametric on two types L and R with two constructors (one taking in an L, and one taking in an R) and two methods L getLeft() and R getRight() such that they either return the value passed when constructing, or throw an exception.

    0 讨论(0)
提交回复
热议问题