Haskell数据类型到Java(OO)

时间:2012-02-29 18:30:53

标签: java haskell

我正在尝试将简单的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数据类型的良好表示吗?

4 个答案:

答案 0 :(得分:5)

让我们尽可能地复制代码。

以下是Haskell数据结构的一些属性:

  1. 它具有Expr类型和两个构造函数LitAdd
  2. 您无法在“外部”
  3. 中添加或删除构造函数

    因此,如果我们希望这些属性在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() {
        ...
    }
}

现在,您可以拥有包含LitAdd个实例的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 LitAdd中没有子类型(在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);
  }
}