这个Haskell函数的Java等效物是什么?

Philip Wadler的论文“函数式编程的Monads”有一个函数eval的例子,它执行用Haskell编写的除法。

这里改编自Graham Hutton的“Programming in Haskell”:

data Expr = Val Int | Div Expr Expr

eval :: Expr -> Int
eval (Val n) = n
eval (Div x y) = eval x `div` eval y

我的 Java 等效于此内容:

abstract class IntegerExpression {
    abstract Integer evaluate();
}

class Value extends IntegerExpression {

    Integer value;

    public Value(Integer x) {
       value = x;
    }

    public  Integer evaluate() {
        return value;
    }
}

class DivisionExpression extends IntegerExpression {

    IntegerExpression x, y;

    public DivisionExpression(IntegerExpression a, IntegerExpression b) {
        x = a;
        y = b;
    }

    public Integer evaluate() {        
        return x.evaluate() / y.evaluate();
    }

}

   public class DivisionExample {

    public static void main(String[] args) {
        IntegerExpression two = new Value(2);
        IntegerExpression twenty = new DivisionExpression (new Value(100), new Value(5));
        IntegerExpression ten = new DivisionExpression(twenty, new Value(2));
        IntegerExpression five = new DivisionExpression(new Value(10), two);
        IntegerExpression expr = new DivisionExpression(ten, five);

        System.out.println(expr.evaluate());
    }
}

这似乎很好,但是我如何开发此代码,以便我可以在Java中演示Try monad(以捕获除以零)?


答案 1

编辑:在这种情况下失败的解决方法是使用,他的表亲在Java中是类,哪里会是和将是。另一方面,在Java和其他O.O.语言中,有一种常见的模式可以在这种称为复合的情况下使用,基本上你的数据类型将是一个接口或抽象类,类型构造函数将是叶子:所以,考虑到所有这些,一个简单的例子是:Maybe MonadOptionalOption.ofreturnflatMapbindExpr

在哈斯克尔:

data Expr = Val Int | Div Expr Expr

eval :: Expr -> Maybe Int
eval (Val n) = Just n
eval (Div x y) = do
                  v1 <- eval x
                  v2 <- eval y
                  if v2 == 0 
                  then Nothing
                  else return (div v1 v2)
n1 = Val 8
n2 = Val 4
n3 = Val 0 
d1 = Div n1 n2
d2 = Div d1 d1
d3 = Div d2 n3

main = do
  putStrLn $ show (eval d2)
  putStrLn $ show (eval d3)

Java 中的等效方法:

import java.util.Optional;

public interface Expr {

    public Optional<Integer> eval();

}

然后叶子实现Expr:

import java.util.Optional;

public class Val implements Expr{

    Optional<Integer> value;

    public Val(int value) {
        this.value = Optional.of(value);
    }

    @Override
    public Optional<Integer> eval() {
        return value;
    }
}

然后是递归情况:

import java.util.Optional;

public class Div implements Expr {

    Expr expr1;
    Expr expr2;

    public Div(Expr expr1, Expr expr2) {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }

    @Override
    public Optional<Integer> eval() {
        return expr1.eval().flatMap(v1 ->
                expr2.eval().flatMap(v2 ->
                    (v2 == 0) ? Optional.empty() : Optional.of(v1 / v2)
                )
               );
    }

    public static void main(String[] args) {
        Expr iv1 = new Val(6);
        Expr iv2 = new Val(3);
        Expr iv3 = new Val(2);
        Expr iv4 = new Val(0);
        Expr div1 = new Div(iv1, iv2);
        Expr div2 = new Div(div1, iv3);
        Expr div3 = new Div(div2, iv4);

        System.out.println(div2.eval());
        System.out.println(div3.eval());

    }
}

主要功能输出为:

Optional[1]
Optional.empty

答案 2

其他答案已经涵盖了在Java中实现它的更习惯性的方式,并描述了如何使用来处理错误。但在这里,我想给出Java中Haskell模式匹配的直接等价物,以及访问者模式Optional

public class ExprTest {
    public static void main(String[] arguments) {

        // expr :: Expr
        // expr = Div
        //   (Div
        //     (Div (Val 100) (Val 5))
        //     (Val 2))
        //   (Div (Val 10) (Val 2))
        Expr two = new Val(2);
        Expr twenty = new Div(new Val(100), new Val(5));
        Expr ten = new Div(twenty, new Val(2));
        Expr five = new Div(new Val(10), two);
        Expr expr = new Div(ten, five);

        // eval :: Expr -> Int
        // eval expr = case expr of
        ExprVisitor<Integer> eval = new ExprVisitor<Integer>() {

            // Val value -> value
            public Integer visit(Val val) {
                return val.value;
            }

            // Div left right -> eval left `div` eval right
            public Integer visit(Div div) {
                return div.left.match(this) / div.right.match(this);
            }

        };

        // main = print (eval expr)
        System.out.println(expr.match(eval));
    }
}

// data Expr
abstract class Expr {
    abstract <T> T match(ExprVisitor<T> visitor);
}

// = Val Int
class Val extends Expr {

    public final int value;

    public Val(int value) {
        this.value = value;
    }

    <T> T match(ExprVisitor<T> visitor) {
        return visitor.visit(this);
    }

}

// | Div Expr Expr
class Div extends Expr {

    public final Expr left, right;

    public Div(Expr left, Expr right) {
        this.left = left;
        this.right = right;
    }

    <T> T match(ExprVisitor<T> visitor) {
        return visitor.visit(this);
    }

}

abstract class ExprVisitor<T> {
    abstract T visit(Val val);
    abstract T visit(Div div);
}

在函数式编程领域,这被称为Böhm-Berarducci编码 - 有时被称为Church编码,尽管它们是不同的东西。这是一种听起来很花哨的说法,即“表示数据类型和模式与函数的匹配”。当然,您可以在Haskell中使用这种匹配编码:

match
  :: (Int -> t)           -- visit(Val)
  -> (Expr -> Expr -> t)  -- visit(Div)
  -> Expr
  -> t
match val div expr = case expr of
  Val x -> val x
  Div left right -> div left right

eval :: Expr -> Int
eval = match id (\ left right -> eval left `div` eval right)

由于是递归的,你也可以使用不动点组合器来编写它 -- 然后在 Java 版本中使用 它可能会变得更加清晰:这就是你如何使递归!evalfixthisExprVisitoreval

import Data.Function (fix)

eval :: Expr -> Int
eval = fix $ \ this -> match
  (\ value -> value)
  (\ left right -> this left `div` this right)

这是编码的另一半:我们可以完全取消数据类型,只使用函数:

{-# LANGUAGE RankNTypes #-}

newtype Expr = Expr
  { visit
    :: forall a.
       (Int -> a)     -- Val
    -> (a -> a -> a)  -- Div
    -> a }

valE :: Int -> Expr
valE x = Expr $ \ v _d -> v x

divE :: Expr -> Expr -> Expr
divE left right = Expr $ \ v d
  -> d (visit left v d) (visit right v d)

eval :: Expr -> Int
eval expr = visit expr
  (\ val -> val)
  (\ left right -> left `div` right)

eval (divE
  (divE (divE (valE 100) (valE 5)) (valE 2))
  (divE (valE 10) (valE 2)))
  == 2

当然,这个实现可以写成这样:eval

eval = visit expr id div