简体   繁体   English

如何在Java中模拟Haskell的“Either a b”

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

How can I write a typesafe Java method that returns either something of class a or something of class b? 我怎样才能编写一个类型安全的Java方法来返回类a或类b的东西? For example: 例如:

public ... either(boolean b) {
  if (b) {
    return new Integer(1);
  } else {
    return new String("hi");
  }
}

What is the cleanest way? 什么是最干净的方式?

( The only thing that comes to my mind is using exceptions which is obviously bad, as it is abusing a error-handling mechanism for a general language feature ... (我想到的唯一一件事就是使用明显不好的异常,因为它滥用了一般语言功能的错误处理机制......

public String either(boolean b) throws IntException {
  if (b) {
    return new String("test");
  } else {
    throw new IntException(new Integer(1));
  }
}

)

My general formula for simulating algebraic data types is: 我模拟代数数据类型的通用公式是:

  • The type is an abstract base class, and the constructors are subclasses of that 类型是抽象基类,构造函数是它的子类
  • The data for each constructor are defined in each subclass. 每个构造函数的数据在每个子类中定义。 (This allows constructors with different numbers of data to work correctly. It also removes the need to maintain invariants like only one variable is non-null or stuff like that). (这允许具有不同数据数量的构造函数正常工作。它还消除了维护不变量的需要,例如只有一个变量是非null或类似的东西)。
  • The constructors of the subclasses serve to construct the value for each constructor. 子类的构造函数用于构造每个构造函数的值。
  • To deconstruct it, one uses instanceof to check the constructor, and downcast to the appropriate type to get the data. 要解构它,可以使用instanceof检查构造函数,然后向下转换为适当的类型以获取数据。

So for Either ab , it would be something like this: 因此,对于Either ab ,它将是这样的:

abstract class Either<A, B> { }
class Left<A, B> extends Either<A, B> {
    public A left_value;
    public Left(A a) { left_value = a; }
}
class Right<A, B> extends Either<A, B> {
    public B right_value;
    public Right(B b) { right_value = b; }
}

// to construct it
Either<A, B> foo = new Left<A, B>(some_A_value);
Either<A, B> bar = new Right<A, B>(some_B_value);

// to deconstruct it
if (foo instanceof Left) {
    Left<A, B> foo_left = (Left<A, B>)foo;
    // do stuff with foo_left.a
} else if (foo instanceof Right) {
    Right<A, B> foo_right = (Right<A, B>)foo;
    // do stuff with foo_right.b
}

Here is a statically checked type-safe solution; 这是一种静态检查的类型安全解决方案; this means you cannot create runtime errors. 这意味着您无法创建运行时错误。 Please read the previous sentence in the way it is meant. 请按照其含义阅读上一句。 Yes, you can provoke exceptions in some way or the other... 是的,你可以用某种方式激起例外......

It's pretty verbose, but hey, it's Java! 它非常冗长,但是嘿,这是Java!

public class Either<A,B> {
    interface Function<T> {
        public void apply(T x);
    }

    private A left = null;
    private B right = null;
    private Either(A a,B b) {
        left = a;
        right = b;
    }

    public static <A,B> Either<A,B> left(A a) {
        return new Either<A,B>(a,null);
    }
    public static <A,B> Either<A,B> right(B b) {
        return new Either<A,B>(null,b);
    }

    /* Here's the important part: */
    public void fold(Function<A> ifLeft, Function<B> ifRight) {
        if(right == null)
            ifLeft.apply(left);
        else
            ifRight.apply(right);
    }

    public static void main(String[] args) {
        Either<String,Integer> e1 = Either.left("foo");
        e1.fold(
                new Function<String>() {
                    public void apply(String x) {
                        System.out.println(x);
                    }
                },
                new Function<Integer>() {
                    public void apply(Integer x) {
                        System.out.println("Integer: " + x);
                    }
                });
    }
}

You might want to look at Functional Java and Tony Morris' blog . 您可能想看看Functional Java和Tony Morris的博客

Here is the link to the implementation of Either in Functional Java. 以下是在Functional Java中实现Either的链接。 The fold in my example is called either there. fold在我的例子被称为either在那里。 They have a more sophisticated version of fold , that is able to return a value (which seems appropriate for functional programming style). 它们有一个更复杂的fold版本,它能够返回一个值(这似乎适合函数式编程风格)。

您可以通过编写一个通用类与哈斯克尔密切的对应Either对两种类型,参数LR与两个构造(一个以在L ,并且在一个了结R和两个方法L getLeft()R getRight() ,使得他们要么返回施工时传递的值,或抛出异常。

The suggestions already provided, although feasible, are not complete as they rely on some null references and effectively make "Either" masquerade as a tuple of values. 已经提供的建议虽然可行,但并不完整,因为它们依赖于一些null引用并有效地使“Either”伪装成值的元组。 A disjoint sum is obviously one type or the other. 不相交的总和显然是一种类型或另一种。

I'd suggest having a look at the implementation of FunctionalJava 's Either as an example. 我建议看看FunctionalJavaEither的实现作为一个例子。

The big thing is not to try to write in one language whilst writing in another. 最重要的是不要试图用另一种语言写一种语言。 Generally in Java you want to put the behaviour in the object, rather than having a "script" running outside with encapsulation destroyed by get methods. 通常在Java中,您希望将行为放在对象中,而不是让“脚本”在外部运行,并且get方法会破坏封装。 There is no context for making that kind of suggestion here. 这里没有提出这种建议的背景。

One safe way of dealing with this particular little fragment is to write it as a callback. 处理这个特定小片段的一种安全方法是将其写为回调。 Similar to a very simple visitor. 类似于一个非常简单的访客。

public interface Either {
    void string(String value);
    void integer(int value);
}

public void either(Either handler, boolean b) throws IntException {
    if (b) {
        handler.string("test");
    } else {
        handler.integer(new Integer(1));
    }
}

You may well want to implement with pure functions and return a value to the calling context. 您可能希望使用纯函数实现并将值返回给调用上下文。

public interface Either<R> {
    R string(String value);
    R integer(int value);
}

public <R> R either(Either<? extends R> handler, boolean b) throws IntException {
    return b ?
        handler.string("test") :
        handler.integer(new Integer(1));
}

(Use Void (capital 'V') if you want to get back to being uninterested in the return value.) (如果你想回到对返回值不感兴趣的话,请使用Void (大写'V')。)

I've implemented it in a Scala-like fashion in the following way. 我已经通过以下方式以类似Scala的方式实现了它。 It's a little verbose (it is Java, after all :)) but it's type safe. 它有点冗长(毕竟它是Java):)但它的类型安全。

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. 然后,您可以在代码上传递Either<A,B>实例。 The Type enum is mainly used on switch statements. Type枚举主要用于switch语句。

Creating Either values is simple as: 创建Either值都很简单:

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. 希望能帮助到你。

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. 来自http://blog.tmorris.net/posts/maybe-in-java/我了解到你可以将外部类的构造函数设为私有,因此只有嵌套类可以对它进行子类化。 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. 这个技巧就像上面最好的类型一样安全,但更简洁,适用于你想要的任何ADT,如Scala的案例类。

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 final s Either has limited width but unbounded depth. 因此没有final s要么宽度有限但是无限深度。

  • With ADTs like this, I see no reason to jump on the whole anti- instanceof bandwagon. 有了这样的ADT的,我认为没有理由跳上全抗instanceof行列。 A boolean works for Maybe or Either, but in general instanceof is your best and only option. 布尔值适用于Maybe或Either,但通常instanceof是您最好的唯一选择。

Thanks to Derive4J algebraic data types are now very easy in Java. 感谢Derive4J代数数据类型现在在Java中非常容易。 All you have to do is create the following class: 您所要做的就是创建以下类:

import java.util.function.Function;

@Data
public abstract class Either<A, B> {

  Either(){}

  /**
   * The catamorphism for either. Folds over this either breaking into left or right.
   *
   * @param left  The function to call if this is left.
   * @param right The function to call if this is right.
   * @return The reduced value.
   */
  public abstract <X> X either(Function<A, X> left, Function<B, X> right);
}

And Derive4J will take care of creating constructors for the left and rights cases, as well as a pattern matching syntax alla Haskell, mapper methods for each sides, and more. Derive4J将负责为左侧和权限案例创建构造函数,以及模式匹配语法alla Haskell,每侧的映射器方法等。

There is a stand-alone implementation of Either for Java 8 in a small library, "ambivalence": http://github.com/poetix/ambivalence 在一个小型库中有一个独立的Either for Java 8实现,“矛盾”: 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. 它最接近Scala标准实现 - 例如,它为maphashMap操作提供左右投影。

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: 相反,您通过提供lambdas将它们映射到单个结果类型来join这两种类型:

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: 你可以从Maven中心获得它:

<dependency>
    <groupId>com.codepoetics</groupId>
    <artifactId>ambivalence</artifactId>
    <version>0.2</version>
</dependency>

You don't need to settle with the instanceof checks or redundant fields. 您无需使用instanceof检查或冗余字段。 Surprisingly enough, Java's type system provides enough features to simulate the sum types cleanly. 令人惊讶的是,Java的类型系统提供了足够的功能来干净地模拟总和类型。

Background 背景

First of all, do you know that any data type can be encoded with just functions? 首先,您是否知道任何数据类型都可以使用函数进行编码? It's called Church encoding . 它被称为教会编码 Eg, using the Haskell signature, the Either type could be defined as follows: 例如,使用Haskell签名, Either类型可以定义如下:

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. 扩展这个想法,在Java中我们可以定义一个名为Matcher的接口,它包含两个函数,然后根据如何在其上进行模式匹配来定义Sum类型。 Here's the complete code: 这是完整的代码:

/**
 * A sum class which is defined by how to pattern-match on it.
 */
public interface Sum2<case1, case2> {

  <output> output match(Matcher<case1, case2, output> matcher);

  /**
   * A pattern-matcher for 2 cases.
   */
  interface Matcher<case1, case2, output> {
    output match1(case1 value);
    output match2(case2 value);
  }

  final class Case1<case1, case2> implements Sum2<case1, case2> {
    public final case1 value;
    public Case1(case1 value) {
      this.value = value;
    }
    public <output> output match(Matcher<case1, case2, output> matcher) {
      return matcher.match1(value);
    }
  }

  final class Case2<case1, case2> implements Sum2<case1, case2> {
    public final case2 value;
    public Case2(case2 value) {
      this.value = value;
    }
    public <output> output match(Matcher<case1, case2, output> 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<Long, Double> longOrDouble) {
    return longOrDouble.match(new Sum2.Matcher<Long, Double, String>() {
      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. 通过这种方法,您甚至可以在Haskell和Scala等语言中找到模式匹配的直接相似性。

Library 图书馆

This code is distributed as part of my library of composite types (Sums and Products, aka Unions and Tuples) of multiple arities. 此代码作为我的多个arities的复合类型库(Sums and Products,又名Unions和Tuples)的一部分进行分发。 It's on GitHub: 它在GitHub上:

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

Since you've tagged Scala, I'll give a Scala answer. 既然你已经标记了Scala,我会给出一个Scala答案。 Just use the existing Either class. 只需使用现有的Either类。 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. 你不会有擦除或类似的问题......如果你根本不能使用Scala,至少可以用它作为你如何实现自己的Either类的一个例子。

Based on the answer by Riccardo, following code snippet worked for me: 根据Riccardo的回答 ,以下代码片段为我工作:

public class Either<L, R> {
        private L left_value;
        private R right_value;
        private boolean right;

        public L getLeft() {
            if(!right) {
                return left_value;
            } else {
                throw new IllegalArgumentException("Left is not initialized");
            }
        }

        public R getRight() {
            if(right) {
                return right_value;
            } else {
                throw new IllegalArgumentException("Right is not initialized");
            }
        }

        public boolean isRight() {
            return right;
        }

        public Either(L left_v, Void right_v) {
           this.left_value = left_v;
           this.right = false;
        }

        public Either(Void left_v, R right_v) {
          this.right_value = right_v;
          right = true;
        }

    }

Usage: 用法:

Either<String, Integer> onlyString = new Either<String, Integer>("string", null);
Either<String, Integer> onlyInt = new Either<String, Integer>(null, new Integer(1));

if(!onlyString.isRight()) {
  String s = onlyString.getLeft();
}

The closest I can think of is a wrapper around both values that lets you check which value is set and retrieve it: 我能想到的最接近的是两个值的包装器,它允许您检查设置的值并检索它:

class Either<TLeft, TRight> {
    boolean isLeft;

    TLeft left;
    TRight right;

    Either(boolean isLeft, TLeft left1, TRight right) {
        isLeft = isLeft;
        left = left;
        this.right = right;
    }

    public boolean isLeft() {
        return isLeft;
    }

    public TLeft getLeft() {
        if (isLeft()) {
            return left;
        } else {
            throw new RuntimeException();
        }
    }

    public TRight getRight() {
        if (!isLeft()) {
            return right;
        } else {
            throw new RuntimeException();
        }
    }

    public static <L, R> Either<L, R> newLeft(L left, Class<R> rightType) {
        return new Either<L, R>(true, left, null);
    }

    public static <L, R> Either<L, R> newRight(Class<L> leftType, R right) {
        return new Either<L, R>(false, null, right);
    }
}

class Main {
    public static void main(String[] args) {
        Either<String,Integer> foo;
        foo = getString();
        foo = getInteger();
    }

    private static Either<String, Integer> getInteger() {
        return Either.newRight(String.class, 123);
    }

    private static Either<String, Integer> getString() {
        return Either.newLeft("abc", Integer.class);
    }   
}

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. 你对返回值做的任何事情都需要某种if / else结构。 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. 从一个快速的谷歌搜索,在我看来,Haskell的Either唯一常用的是错误报告,所以看起来异常实际上是纠正替换。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM