我正在尝试将简单的Haskell数据类型和函数转换为OO。 但我很困惑..
具有以下Haskell类型用于算术运算:
data Expr = Lit Int |
Add Expr Expr |
deriving Show
--Turn the expr to a nice string
showExpr :: Expr -> String
showExpr (Lit n) = show n
showExpr (Add e1 e2) = "(" ++ showExpr e1 ++ "+" ++ showExpr e2 ++ ")"
现在我正在尝试转换..
public interface Expr {
String showExpr(String n);
}
// Base case
public class Lit implements Expr {
String n;
public Lit(String n) {
this.n = n;
}
@Override
public String ShowExpr() {
return n;
}
}
public class Add implements Expr {
Expr a;
Expr b;
public Add(Expr aExpr, Expr bExpr) {
this.a = aExpr;
this.b = bExpr;
}
public String ShowExpr() {
return "(" + a.ShowExpr() + "+" + b.ShowExpr() + ")";
}
public static void main(String[] args) {
Lit firstLit = new Lit("2");
Lit secLit = new Lit("3");
Add add = new Add(firstLit,secLit);
System.out.println(add.ShowExpr());
}
}
这将导致“(2 + 3)”,这是正确的答案。
但是......我不确定..这是考虑它并在OO中建模的正确方法吗?
不是Haskell数据类型的良好表示吗?
答案 0 :(得分:5)
让我们尽可能地复制代码。
以下是Haskell数据结构的一些属性:
Expr
类型和两个构造函数Lit
和Add
因此,如果我们希望这些属性在Java版本中保持正确,您应该这样做:
public abstract class Expr {
// So that you can't add more subclasses outside this block
private Expr() {}
// Simulate pattern matching:
// (This CAN be done with instanceof, but that's ugly and not OO)
public boolean isLit() {
return false;
}
public boolean isAdd() {
return false;
}
public Lit asLit() {
throw new UnsupportedOperationException("This is not a Lit");
}
public Add asAdd() {
throw new UnsupportedOperationException("This is not an Add");
}
public static class Lit extends Expr {
public final int n;
public Lit(int n) {
this.n = n;
}
@Override
public boolean isLit() {
return true;
}
@Override
public Lit asLit() {
return this;
}
}
public static class Add extends Expr {
public final Expr a, b;
public Lit(Expr a, Expr b) {
this.a = a;
this.b = b;
}
@Override
public boolean isAdd() {
return true;
}
@Override
public Add asAdd() {
return this;
}
}
}
现在,要转换showExpr
:
public static String showExpr(final Expr expr) {
if(expr.isLit()) {
return Integer.toString(expr.asLit().n);
} else if(expr.isAdd()) {
return "(" + expr.asAdd().a + "+" + expr.asAdd().b + ")";
}
}
您可以将showExpr
作为静态方法放在Expr
类中。我不使它成为一个实例方法,因为它远离Haskell版本。
答案 1 :(得分:2)
我会略微转过头来看:每个Haskell类都成为一个Java接口,instance ...where
(或derives
)变为implements
。
所以,class Show a
变为
interface Showable {
String show();
}
现在,Expr
数据类型有什么作用?它将各种Haskell构造函数(也称为Java类)组合到一个类型中,并且还说它们都被Haskell类所涵盖(即实现Java接口)。那么,我会说数据类型Expr
变为
interface Expr extends Showable {}
例如,和Lit
构造函数变为:
class Lit implements Expr {
@Override
String show() {
...
}
}
现在,您可以拥有包含Lit
和Add
个实例的Java集合(例如List<Expr>
),对于任何Expr
,您知道它implements Showable
请注意,通过将Showable
作为自己的接口(而不是将其方法直接放在Expr
上),我允许其他完全不相关的Java类也实现它。这类似于任何人都可以定义您定义的任何Haskell类的instance ... where
。
最后,这里的开放世界和封闭世界政策之间存在一些不匹配。 Haskell的instance ... where
是开放世界,而Java的implements
是封闭的。但是Haskell的构造函数是每个类型是关闭的,而Java的extends
主要是打开。
关于implements
被关闭,你无能为力,但你可以用抽象类和私有构造函数关闭extends
,这就是我要做的事情(如果你想要那个行为) 。稍微调整一下,我们得到:
public abstract class Expr implements Showable {
private Expr() {
// hide the ctor from the outside world, so nobody else
// can extend this class
}
public static class Lit extends Expr {
...
}
public static class Add extends Expr {
...
}
}
答案 2 :(得分:1)
我会这样翻译:
public abstract class Expr {
public static Expr Lit(final int n) {
return new Expr() {
public String showExpr() {
return "" + n;
}
};
}
public static Expr Add(final Expr e1, final Expr e2) {
return new Expr() {
public String showExpr() {
return String.format("(%s+%s)", e1.showExpr(), e2.showExpr());
}
};
}
public abstract String showExpr();
public static void main(String[] args) {
Expr firstLit = Lit(2);
Expr secLit = Lit(3);
Expr add = Add(firstLit, secLit);
System.out.println(add.showExpr());
}
}
在Haskell Lit
和Add
中没有子类型(在Haskell中没有这样的东西),只有Expr
次。没有必要在Java中公开子类,为什么我可以使用隐藏在某些方法中的匿名类。这是完美的,只要你不需要模式匹配(无论如何都很难在Java中建模,因为简单的instanceof
测试很快就会遇到更复杂的例子)。
答案 3 :(得分:0)
您的代码看起来不错,但可能更加惯用。例如,您可以使用final
使字段不可变。您也可以将showExp
替换为toString
(在Object
中声明),除非您希望toString
执行不同的操作。在我看来,使用String.format
比连接几个字符串更清晰。最后,我会Lit
存储int
,因为它更紧凑且类型安全。
以下是我将如何翻译它:
abstract class Exp {
// Force subclasses to override Object.toString.
public abstract String toString();
}
class Lit extends Exp {
final int value;
Lit(int value) {
this.value = value;
}
public String toString() {
return Integer.toString(value);
}
}
class Add extends Exp {
final Exp a, b;
Add(Exp a, Exp b) {
this.a = a;
this.b = b;
}
public String toString() {
return String.format("(%s + %s)", a, b);
}
}
class Main {
public static void main(String[] args) {
Lit firstLit = new Lit(2);
Lit secLit = new Lit(3);
Add add = new Add(firstLit, secLit);
System.out.println(add);
}
}