Java 如何模拟Haskell';s";要么是“b”;在爪哇

Java 如何模拟Haskell';s";要么是“b”;在爪哇,java,haskell,Java,Haskell,如何编写一个类型安全的Java方法来返回类a或类b?例如: public ... either(boolean b) { if (b) { return new Integer(1); } else { return new String("hi"); } } 最干净的方法是什么 (我唯一想到的是使用异常,这显然是不好的,因为它滥用了一般语言特性的错误处理机制 public String either(boolean b) throws IntException {

如何编写一个类型安全的Java方法来返回类a或类b?例如:

public ... either(boolean b) {
  if (b) {
    return new Integer(1);
  } else {
    return new String("hi");
  }
}
最干净的方法是什么

(我唯一想到的是使用异常,这显然是不好的,因为它滥用了一般语言特性的错误处理机制

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

)

您可以通过编写一个泛型类
,在两种类型
L
R
上使用两个构造函数(一个接受
L
,一个接受
R
)和两个方法
L getLeft()
R getRight())与Haskell建立密切的对应关系
这样它们要么返回构造时传递的值,要么抛出异常。

更改您的设计,这样您就不需要这个相当荒谬的功能。对返回值执行的任何操作都需要某种if/else构造。那会非常非常难看


通过快速的谷歌搜索,我觉得Haskell's Orther唯一常用的功能就是错误报告,所以看起来异常实际上是为了纠正替换。我能想到的最接近的方法是在两个值周围都有一个包装器,它可以让你检查设置了哪个值并检索它:

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);
    }   
}
类{
布尔isLeft;
左撇子;
三权;
或者(布尔值isLeft、TLeft left1、TRight right){
isLeft=isLeft;
左=左;
这个。右=右;
}
公共布尔值isLeft(){
返回isLeft;
}
公共图书馆{
if(isLeft()){
左转;
}否则{
抛出新的RuntimeException();
}
}
公共TRight getRight(){
如果(!isLeft()){
返还权;
}否则{
抛出新的RuntimeException();
}
}
公共静态或newLeft(L left,类rightType){
返回新的(true、left、null);
}
公共静态或newRight(类leftType,R right){
返回新的(false、null、right);
}
}
班长{
公共静态void main(字符串[]args){
要么是福;
foo=getString();
foo=getInteger();
}
私有静态getInteger(){
返回.newRight(String.class,123)之一;
}
私有静态getString(){
返回.newLeft(“abc”,Integer.class);
}   
}

重要的是不要试图用一种语言写作,而用另一种语言写作。通常,在Java中,您希望将行为放在对象中,而不是让“脚本”在外部运行,而get方法破坏了封装。这里没有提出这种建议的背景

处理这个小片段的一种安全方法是将其作为回调编写。类似于一个非常简单的访问者

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));
    }
}
您可能希望使用纯函数实现,并向调用上下文返回一个值

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));
}
公共接口{
R字符串(字符串值);
R整数(int值);
}

public R other(other既然您已经标记了Scala,我将给出Scala的答案。只需使用现有类即可。下面是一个示例用法:

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)
}
这是完全类型安全的;您不会有擦除或类似的问题。。。
如果您无法使用Scala,至少可以将此作为一个示例,说明如何实现自己的
类。

这里是一个静态检查的类型安全解决方案;这意味着您无法创建运行时错误。请按其意思阅读前一句话。是的,您可以以某种方式引发异常

这很冗长,但嘿,这是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);
                    }
                });
    }
}
public class或者托尼·莫里斯


是指向在Functional Java中实现
Order
的链接。我的示例中的
fold
在那里被称为
Order
。它们有一个更复杂的
fold
,能够返回一个值(这似乎适合于函数式编程风格).

已经提供的建议虽然可行,但并不完整,因为它们依赖于一些
null
引用,并有效地将“要么”伪装成一个值元组。不相交和显然是一种或另一种类型


我建议以“s
的实现为例。

我以类似于Scala的方式用以下方式实现了它。它有点冗长(毕竟是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;
    }
  }
}
或者,在使用它而不是异常的典型情况下

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;
}
public或doSomething(){
//伪码
如果(确定){
结果值=。。。
返回其中一个。右(值);
}否则{
错误errorMsg=。。。
返回其中一个.left(errorMsg);
}
}
//在代码的某个地方。。。
任一结果=剂量测定();
开关(result.getType()){
case Choice.Type.LEFT:
//处理错误
errerrorvalue=result.leftValue().value();
打破
case Choice.Type.RIGHT:
//过程结果
Res resultValue=result.rightValue().value();
打破
}

希望有帮助。

我模拟代数数据类型的一般公式是:

  • 类型是一个抽象基类,构造函数是它的子类
  • 每个构造函数的数据在每个子类中定义。(这允许具有不同数量数据的构造函数正确工作。它还消除了维护不变量的需要,例如只有一个变量是非空的或类似的东西)
  • 子类的构造函数用于为每个构造函数构造值
  • 要解构它,可以使用
    instanceof
    检查构造函数,并向下转换到适当的类型以获取数据
因此,对于
或a b
,它将是这样的:

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
}
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() + ")";
      }
    });
  }

}
抽象类{}
类左扩展了它
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; }
    }
}
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;
        }

    }
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();
}
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);
}
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);
<dependency>
    <groupId>com.codepoetics</groupId>
    <artifactId>ambivalence</artifactId>
    <version>0.2</version>
</dependency>
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() + ")";
      }
    });
  }

}