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

后端 未结 14 2197
梦毁少年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:11

    You don't need to settle with the instanceof checks or redundant fields. Surprisingly enough, Java's type system provides enough features to simulate the sum types cleanly.

    Background

    First of all, do you know that any data type can be encoded with just functions? It's called Church encoding. E.g., using the Haskell signature, the Either type could be defined as follows:

    type Either left right =
      forall output. (left -> output) -> (right -> output) -> output
    

    You can interpret it as "given a function on the left value and a function on the right value, produce the result of either of them".

    Definition

    Expanding on this idea, in Java we can define an interface called Matcher, which includes both functions and then define the Sum type in terms of how to pattern-match on it. Here's the complete code:

    /**
     * A sum class which is defined by how to pattern-match on it.
     */
    public interface Sum2 {
    
       output match(Matcher matcher);
    
      /**
       * A pattern-matcher for 2 cases.
       */
      interface Matcher {
        output match1(case1 value);
        output match2(case2 value);
      }
    
      final class Case1 implements Sum2 {
        public final case1 value;
        public Case1(case1 value) {
          this.value = value;
        }
        public  output match(Matcher matcher) {
          return matcher.match1(value);
        }
      }
    
      final class Case2 implements Sum2 {
        public final case2 value;
        public Case2(case2 value) {
          this.value = value;
        }
        public  output match(Matcher matcher) {
          return matcher.match2(value);
        }
      }
    
    }
    

    Usage

    And then you can use it like this:

    import junit.framework.TestCase;
    
    public class Test extends TestCase {
    
      public void testSum2() {
        assertEquals("Case1(3)", longOrDoubleToString(new Sum2.Case1<>(3L)));
        assertEquals("Case2(7.1)", longOrDoubleToString(new Sum2.Case2<>(7.1D)));
      }
    
      private String longOrDoubleToString(Sum2 longOrDouble) {
        return longOrDouble.match(new Sum2.Matcher() {
          public String match1(Long value) {
            return "Case1(" + value.toString() + ")";
          }
          public String match2(Double value) {
            return "Case2(" + value.toString() + ")";
          }
        });
      }
    
    }
    

    With this approach you can even find a direct resemblance of pattern-matching in such languages as Haskell and Scala.

    Library

    This code is distributed as part of my library of composite types (Sums and Products, aka Unions and Tuples) of multiple arities. It's on GitHub:

    https://github.com/nikita-volkov/composites.java

提交回复
热议问题