有没有办法在Java中定义sum类型?Java似乎天生就直接支持乘积类型,我认为枚举可能允许它支持sum类型,而继承看起来可能可以做到这一点,但至少有一种情况我无法解决。详细说明一下,sum类型是一种类型,它可以只拥有一组不同类型中的一个,就像C中的标记联合。在我的情况下,我正在尝试在Java中实现Haskell的Either type:
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
值才有可能,我希望完全避免这种情况,除非有一种方法可以安全地一次完成,并能够对所有求和类型使用。
6条答案
按热度按时间b4lqfgs41#
使
Either
成为一个抽象类,没有字段,只有一个构造函数(private、no-args、empty),并将“数据构造函数”(left
和right
静态工厂方法)嵌套在类中,这样它们就可以看到私有构造函数,而其他任何方法都看不到,从而有效地密封了类型。使用抽象方法
either
来模拟穷举模式匹配,在静态工厂方法返回的具体类型中进行适当的重写。根据either
实现方便的方法(如fromLeft
、fromRight
、bimap
、first
、second
)。令人愉快和安全!没有办法搞砸它。因为类型是有效密封的,所以您可以放心,永远只会有两种情况,并且每个操作最终都必须根据
either
方法定义,这迫使调用方处理这两种情况。关于你试图做
class Left<L> extends Either<L,?>
的问题,考虑一下签名<A, B> Either<A, B> left(A value)
。类型参数B
没有出现在参数列表中。所以,给定一个A
类型的值,你可以得到一个Either<A, B>
,用于 * 任何 *B
类型。eulz3vhy2#
一个标准的编码和类型的方法是Boehm-Berarducci编码(通常被称为它的表亲,教堂编码的名字),它表示一个代数数据类型作为它的 * 消除器 *,即,一个函数,做模式匹配。在Haskell:
在Java中,这看起来像一个访问者:
示例用法:
为了方便起见,您可以创建一个工厂来创建
Left
和Right
值,而不必每次都提到类型参数;如果您希望模式匹配选项不产生结果,您还可以添加接受Consumer<A> left, Consumer<B> right
而不是Function<A, R> left, Function<B, R> right
的match
版本。nhhxz33t3#
好的,继承解决方案无疑是最有前途的。我们想做的是
class Left<L> extends Either<L, ?>
,但不幸的是,由于Java的泛型规则,我们不能这样做。然而,如果我们做出让步,Left
或Right
的类型必须编码“替代”的可能性,我们可以这样做。现在,我们希望能够将
Left<Integer, A>
转换为Left<Integer, B>
,因为它实际上并没有 * 使用 * 第二个类型参数。我们可以定义一个方法在内部进行这种转换,从而将这种自由编码到类型系统中。完整示例:
当然,您需要添加一些函数来实际访问内容,并检查值是
Left
还是Right
,这样就不必到处使用instanceof
和显式强制转换,但至少这应该足以开始。fae0ux8s4#
继承 * 可以 * 用于模拟求和类型(不相交联合),但有几个问题需要处理:
1.你需要注意防止其他人在你的类型中添加新的case。如果你想彻底地处理你可能遇到的每一个case,这一点尤其重要。对于非final超类和包私有构造函数来说,这是可能的。
1.缺少模式修补使得使用这种类型的值非常困难。如果你想用编译器检查的方法来保证你已经彻底处理了所有的情况,你需要自己实现一个匹配函数。
Optional.get()
。理想情况下,该方法只能在已知值为some
而不是none
的不相交类型上使用。但没有办法做到这一点。所以它是一般Optional
型别的执行严修成员。如果您在选择项上呼叫它,而选择项的case是none,它就会掷回NoSuchElementException
。TL;DR:用Java进行函数式编程并不是一种愉快的体验。
5ssjco0h5#
不如
与这里提出的其他解决方案的区别不是很深,而是风格上的。
cngwdvgl6#
让我建议一个非常不同的解决方案,它不使用继承/抽象类/接口。缺点是,它需要为每个新定义的“求和类型”付出一些努力。然而,我认为它有很多优点:它是安全的,它只使用基本概念,使用起来感觉很自然,并且允许2个以上的“子类型”。
这里是二叉树的概念证明,因为它比“Either”更实用,但是您可以使用注解作为指导来构建自己的sum类型。
请随意提出批评,我对这个主题真的很感兴趣,很乐意了解更多。