有没有办法在Java中定义和类型? Java似乎自然地直接支持产品类型,我认为枚举可能允许它支持和类型,并且继承看起来可能它可以做到,但至少有一个案例我无法解决。 详细说明,sum类型是一种类型,它可以具有一组不同类型中的一种,如C中的标记并集。 就我而言,我试图在Java中实现haskell的任何一种类型:
data Either a b = Left a | Right b
但在基层,我必须将其作为产品类型实施,而忽略其中的一个字段:
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");
}
}
.
.
.
}
我尝试使用继承来实现它,但我必须使用通配符类型参数或等效的Java泛型不允许的参数:
public class Left<L> extends Either<L,?>
我还没有多少使用过Java的Enums,但他们看起来是次佳候选人,但我并不乐观。
在这一点上,我认为这可能只能通过类型转换Object
值来实现,我希望完全避免这些值,除非有一种方法可以安全地执行,并且能够使用它适用于所有类型。
答案 0 :(得分:51)
使用一个私有构造函数创建Either
一个抽象类,并在类中嵌套“数据构造函数”(left
和right
静态工厂方法),以便它们可以看到私有构造函数,但没有别的可以,有效地密封类型。
使用抽象方法either
来模拟详尽的模式匹配,适当地覆盖静态工厂方法返回的具体类型。根据{{1}}实现便捷方法(如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());
}
// other convenience methods
}
的问题,请考虑签名class Left<L> extends Either<L,?>
。类型参数<A, B> Either<A, B> left(A value)
未出现在参数列表中。因此,给定某种类型B
的值,您可以为任何类型A
获取Either<A, B>
。
答案 1 :(得分:19)
编码和类型的标准方法是Boehm-Berarducci编码(通常由其堂兄的名称,Church编码),它代表代数数据类型作为其消除器,即函数做模式匹配。在哈斯克尔:
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
在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);
}
}
使用示例:
Either<Integer, String> result = new Left<Integer, String>(42);
String message = result.match(
errorCode -> "Error: " + errorCode.toString(),
successMessage -> successMessage);
为方便起见,您可以创建工厂来创建Left
和Right
值,而无需每次都提及类型参数;如果您希望选择模式匹配而不产生结果,则还可以添加match
的{{1}}版本,而不是Consumer<A> left, Consumer<B> right
。
答案 2 :(得分:6)
好的,所以继承解决方案绝对是最有希望的。我们想要做的是class Left<L> extends Either<L, ?>
,由于Java的通用规则,我们遗憾地无法做到这一点。但是,如果我们做出让步,Left
或Right
的类型必须编码&#34;替代&#34;可能性,我们可以这样做。
public class Left<L, R> extends Either<L, R>`
现在,我们希望能够将Left<Integer, A>
转换为Left<Integer, B>
,因为它实际上并未使用第二个类型参数。我们可以定义一种在内部进行此转换的方法,从而将该自由编码到类型系统中。
public <R1> Left<L, R1> phantom() {
return new Left<L, R1>(contents);
}
完整示例:
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);
}
}
}
当然,您需要添加一些功能来实际访问内容,并检查值是Left
还是Right
,这样您就不必撒上instanceof
和各地的明确演员表,但这至少应该足以开始。
答案 3 :(得分:1)
继承可以用于模拟和类型(Disjoint unions),但是您需要处理一些问题:
Optional.get()
。理想情况下,此方法仅适用于已知值为some
而不是none
的不相交类型。但是没有办法做到这一点,因此它是一般Optional
类型的实例成员。如果你在一个名为“none”的可选项上调用它,它会抛出NoSuchElementException
。TL; DR:Java中的函数式编程并不是一种愉快的体验。