![](/img/trans.png)
[英]How to simulate Haskell's pattern matching using visitor pattern in Java
[英]How can I simulate Haskell's “Either a b” in Java
我怎样才能编写一个类型安全的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));
}
}
)
我模拟代数数据类型的通用公式是:
instanceof
检查构造函数,然后向下转换为适当的类型以获取数据。 因此,对于Either ab
,它将是这样的:
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
}
这是一种静态检查的类型安全解决方案; 这意味着您无法创建运行时错误。 请按照其含义阅读上一句。 是的,你可以用某种方式激起例外......
它非常冗长,但是嘿,这是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);
}
});
}
}
您可能想看看Functional Java和Tony Morris的博客 。
以下是在Functional Java中实现Either
的链接。 该fold
在我的例子被称为either
在那里。 它们有一个更复杂的fold
版本,它能够返回一个值(这似乎适合函数式编程风格)。
您可以通过编写一个通用类与哈斯克尔密切的对应Either
对两种类型,参数L
和R
与两个构造(一个以在L
,并且在一个了结R
和两个方法L getLeft()
和R getRight()
,使得他们要么返回施工时传递的值,或抛出异常。
已经提供的建议虽然可行,但并不完整,因为它们依赖于一些null
引用并有效地使“Either”伪装成值的元组。 不相交的总和显然是一种类型或另一种。
我建议看看FunctionalJava的Either
的实现作为一个例子。
最重要的是不要试图用另一种语言写一种语言。 通常在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));
}
(如果你想回到对返回值不感兴趣的话,请使用Void
(大写'V')。)
我已经通过以下方式以类似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;
}
}
}
然后,您可以在代码上传递Either<A,B>
实例。 Type
枚举主要用于switch
语句。
创建Either
值都很简单:
Either<A, B> underTest;
A value = new A();
underTest = Either.left(value);
assertEquals(Choice.Type.LEFT, underTest.getType());
assertSame(underTest, underTest.leftValue());
assertNull(underTest.rightValue());
assertSame(value, underTest.leftValue().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;
}
希望能帮助到你。
来自http://blog.tmorris.net/posts/maybe-in-java/我了解到你可以将外部类的构造函数设为私有,因此只有嵌套类可以对它进行子类化。 这个技巧就像上面最好的类型一样安全,但更简洁,适用于你想要的任何ADT,如Scala的案例类。
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; }
}
}
(从最佳答案的代码和风格开始)
注意:
子类的决赛是可选的。 如果没有它们,您可以左键和右键,但仍然不能直接键入。 因此没有final
s要么宽度有限但是无限深度。
有了这样的ADT的,我认为没有理由跳上全抗instanceof
行列。 布尔值适用于Maybe或Either,但通常instanceof
是您最好的唯一选择。
感谢Derive4J代数数据类型现在在Java中非常容易。 您所要做的就是创建以下类:
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);
}
Derive4J将负责为左侧和权限案例创建构造函数,以及模式匹配语法alla Haskell,每侧的映射器方法等。
在一个小型库中有一个独立的Either
for Java 8实现,“矛盾”: http : //github.com/poetix/ambivalence
它最接近Scala标准实现 - 例如,它为map
和hashMap
操作提供左右投影。
无法直接访问左侧或右侧值; 相反,您通过提供lambdas将它们映射到单个结果类型来join
这两种类型:
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);
你可以从Maven中心获得它:
<dependency>
<groupId>com.codepoetics</groupId>
<artifactId>ambivalence</artifactId>
<version>0.2</version>
</dependency>
您无需使用instanceof
检查或冗余字段。 令人惊讶的是,Java的类型系统提供了足够的功能来干净地模拟总和类型。
首先,您是否知道任何数据类型都可以使用函数进行编码? 它被称为教会编码 。 例如,使用Haskell签名, Either
类型可以定义如下:
type Either left right =
forall output. (left -> output) -> (right -> output) -> output
您可以将其解释为“给定左值上的函数和右值上的函数,生成其中任何一个的结果”。
扩展这个想法,在Java中我们可以定义一个名为Matcher
的接口,它包含两个函数,然后根据如何在其上进行模式匹配来定义Sum类型。 这是完整的代码:
/**
* A sum class which is defined by how to pattern-match on it.
*/
public interface Sum2<case1, case2> {
<output> output match(Matcher<case1, case2, output> matcher);
/**
* A pattern-matcher for 2 cases.
*/
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等语言中找到模式匹配的直接相似性。
此代码作为我的多个arities的复合类型库(Sums and Products,又名Unions和Tuples)的一部分进行分发。 它在GitHub上:
既然你已经标记了Scala,我会给出一个Scala答案。 只需使用现有的Either
类。 这是一个示例用法:
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,至少可以用它作为你如何实现自己的Either
类的一个例子。
根据Riccardo的回答 ,以下代码片段为我工作:
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();
}
我能想到的最接近的是两个值的包装器,它允许您检查设置的值并检索它:
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);
}
}
改变你的设计,这样你就不需要这个荒谬的功能了。 你对返回值做的任何事情都需要某种if / else结构。 它会非常非常难看。
从一个快速的谷歌搜索,在我看来,Haskell的Either唯一常用的是错误报告,所以看起来异常实际上是纠正替换。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.