Java标记了union/sum类型

Zoe*_*wll 40 java haskell algebraic-data-types

有没有办法在Java中定义sum类型?Java似乎直接支持产品类型,我认为枚举可能允许它支持和类型,并且继承看起来可能它可以做到,但至少有一个我无法解决的情况.详细说明,sum类型是一种类型,它可以恰好具有一组不同类型中的一种,例如C中的标记联合.在我的例子中,我试图在Java中实现haskell的Either类型:

data Either a b = Left a | Right b
Run Code Online (Sandbox Code Playgroud)

但是在基础级别我不得不将其作为产品类型实现,而忽略其中一个字段:

public class Either<L,R>
{
    private L left = null;
    private R right = null;

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

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

    private Either(L left, R right) throws IllegalArgumentException
    {
        this.left = left;
        this.right = right;
        if (left != null && right != null)
        {
            throw new IllegalArgumentException("An Either cannot be created with two values");
        }
        if (left == right)
        {
            throw new IllegalArgumentException("An Either cannot be created without a value");
        }
    }

    .
    .
    .
}
Run Code Online (Sandbox Code Playgroud)

我尝试使用继承来实现它,但是我必须使用Java泛型不允许的通配符类型参数或等效参数:

public class Left<L> extends Either<L,?>
Run Code Online (Sandbox Code Playgroud)

我没有太多使用Java的Enums,但是虽然它们似乎是次佳的候选者,但我并不抱希望.
在这一点上,我认为这可能只能通过类型转换Object值来实现,我希望完全避免这种情况,除非有一种方法可以安全地执行,并且能够将其用于所有和类型.

gde*_*ohn 52

Either一个私人的构造函数和嵌套你的"数据构造"(一个抽象类leftright静态工厂方法)的类里面,使他们可以看到私有的构造,但没有别的可以有效地密封型.

使用抽象方法either模拟详尽的模式匹配,在静态工厂方法返回的具体类型中适当地覆盖.实现方便的方法(如fromLeft,fromRight,bimap,first,second而言)either.

import java.util.Optional;
import java.util.function.Function;

public abstract class Either<A, B> {
    private Either() {}

    public abstract <C> C either(Function<? super A, ? extends C> left,
                                 Function<? super B, ? extends C> right);

    public static <A, B> Either<A, B> left(A value) {
        return new Either<>() {
            @Override
            public <C> C either(Function<? super A, ? extends C> left,
                                Function<? super B, ? extends C> right) {
                return left.apply(value);
            }
        };
    }

    public static <A, B> Either<A, B> right(B value) {
        return new Either<>() {
            @Override
            public <C> C either(Function<? super A, ? extends C> left,
                                Function<? super B, ? extends C> right) {
                return right.apply(value);
            }
        };
    }

    public Optional<A> fromLeft() {
        return this.either(Optional::of, value -> Optional.empty());
    }
}
Run Code Online (Sandbox Code Playgroud)

愉快又安全!没办法搞砸了.

关于您尝试过的问题either,请考虑签名class Left<L> extends Either<L,?>.type参数<A, B> Either<A, B> left(A value)不会出现在参数列表中.因此,考虑到某种类型的值B,你可以得到一个A任何类型Either<A, B>.


Jon*_*rdy 19

编码和类型的标准方法是Boehm-Berarducci编码(通常由其堂兄的名称,Church编码),它代表代数数据类型作为其消除器,即执行模式匹配的函数.在Haskell:

left :: a -> (a -> r) -> (b -> r) -> r
left x l _ = l x

right :: b -> (a -> r) -> (b -> r) -> r
right x _ r = r x

match :: (a -> r) -> (b -> r) -> ((a -> r) -> (b -> r) -> r) -> r
match l r k = k l r

-- Or, with a type synonym for convenience:

type Either a b r = (a -> r) -> (b -> r) -> r

left :: a -> Either a b r
right :: b -> Either a b r
match :: (a -> r) -> (b -> r) -> Either a b r -> r
Run Code Online (Sandbox Code Playgroud)

在Java中,这看起来像访问者:

public interface Either<A, B> {
    <R> R match(Function<A, R> left, Function<B, R> right);
}

public final class Left<A, B> implements Either<A, B> {

    private final A value;

    public Left(A value) {
        this.value = value;
    }

    public <R> R match(Function<A, R> left, Function<B, R> right) {
        return left.apply(value);
    }

}

public final class Right<A, B> implements Either<A, B> {

    private final B value;

    public Right(B value) {
        this.value = value;
    }

    public <R> R match(Function<A, R> left, Function<B, R> right) {
        return right.apply(value);
    }

}
Run Code Online (Sandbox Code Playgroud)

用法示例:

Either<Integer, String> result = new Left<Integer, String>(42);
String message = result.match(
  errorCode -> "Error: " + errorCode.toString(),
  successMessage -> successMessage);
Run Code Online (Sandbox Code Playgroud)

为方便起见,您可以创建一个工厂来创建LeftRight值,而不必每次都提到类型参数; 您也可以添加一个match接受的版本,Consumer<A> left, Consumer<B> right而不是Function<A, R> left, Function<B, R> right如果您想要模式匹配选项而不产生结果.

  • 这种方法的一个主要卖点是不需要使用类型转换(也不是`instanceof`)来消除和类型.换句话说,这也适用于Java的一个片段,其中禁止使用类型转换等潜在危险的构造. (4认同)

Sil*_*olo 7

好吧,所以继承解决方案绝对是最有希望的.我们想要做的是class Left<L> extends Either<L, ?>,由于Java的通用规则,我们遗憾地无法做到这一点.但是,如果我们做出让步Left或者Right必须编码"替代"可能性的让步,我们就可以做到这一点.

public class Left<L, R> extends Either<L, R>`
Run Code Online (Sandbox Code Playgroud)

现在,我们希望能够转换Left<Integer, A>Left<Integer, B>,因为它实际上并没有使用第二个类型参数.我们可以定义一种在内部进行此转换的方法,从而将该自由编码到类型系统中.

public <R1> Left<L, R1> phantom() {
  return new Left<L, R1>(contents);
}
Run Code Online (Sandbox Code Playgroud)

完整的例子:

public class EitherTest {

  public abstract static class Either<L, R> {}

  public static class Left<L, R> extends Either<L, R> {

    private L contents;

    public Left(L x) {
      contents = x;
    }

    public <R1> Left<L, R1> phantom() {
      return new Left<L, R1>(contents);
    }

  }

  public static class Right<L, R> extends Either<L, R> {

    private R contents;

    public Right(R x) {
      contents = x;
    }

    public <L1> Right<L1, R> phantom() {
      return new Right<L1, R>(contents);
    }

  }

}
Run Code Online (Sandbox Code Playgroud)

当然,你要添加一些功能实际上访问的内容,以及用于检查值是否Left还是Right这样你就不必洒instanceof和显式转换无处不在,但是这应该是足够的上手,在最最小.

  • 没什么特别的.只需将其全部包装在一个文件中,这样所有类都可以公开.Java要求每个文件最多只有一个公共类.如果在项目中实现此解决方案,则没有理由使用"EitherTest". (2认同)