你不需要使用 instanceof 检查或冗余字段。令人惊讶的是,Java 的类型系统提供了足够的功能来清晰地模拟 sum types。
背景
首先,你知道任何数据类型都可以用函数进行编码吗?这被称为
Church编码。例如,使用 Haskell 签名,Either 类型可以定义如下:
type Either left right =
forall output. (left -> output) -> (right -> output) -> output
你可以将其解释为“给定左值上的函数和右值上的函数,生成任意一个函数的结果”。
定义:
基于这个想法,在Java中我们可以定义一个名为Matcher的接口,它包括两个函数,然后根据如何在Sum类型上进行模式匹配来定义Sum类型。下面是完整的代码:
public interface Sum2<case1, case2> {
<output> output match(Matcher<case1, case2, output> matcher);
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);
}
}
}
使用方法
然后你可以像这样使用它:
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() + ")";
}
});
}
}
使用这种方法,您甚至可以在像Haskell和Scala这样的语言中找到模式匹配的直接相似之处。
库
此代码作为我的复合类型(和积,又称联合和元组)的库的一部分进行分发。它位于GitHub上:
https://github.com/nikita-volkov/composites.java