【问题标题】:What is the Java equivalent of this Haskell function?这个 Haskell 函数的 Java 等价物是什么?
【发布时间】:2018-11-30 17:14:21
【问题描述】:

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

这里改编自 Graham Hutton 的“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(以捕获除零)?

【问题讨论】:

  • 为什么不在 Java 代码中使用异常?此外,Haskell 代码实际上并不处理除以零。如果你直接翻译,我不明白你为什么要处理。
  • 如果你想发挥作用:java中有一个Optional类
  • @DánielSomogyi 感谢您的评论让我更好地理解了这里的问题
  • 我会使用 Java 接口而不是抽象类。还要知道,理论上,任何代数数据类型(如 Haskell 的 datas)都可以使用“访问者模式”/“指示性编码”/“Church 编码”在 Java 中忠实地表示。像上面那样使用继承很接近,但不一样,因为类是开放的(可以添加新的子类)。此外,必须在所有子类上传播模式匹配有点不方便(访问者模式避免了这种情况)。主要缺点是访问者模式不像上述方法那么简单。

标签: java haskell java-8 functional-programming monads


【解决方案1】:

编辑:这种情况下失败的解决方法是使用Maybe Monad,他在Java中的表亲是Optional类,其中Option.of是return,flatMap是bind。另一方面,在 Java 和其他 O.O.在这种情况下,有一种通用模式可以使用,称为复合,基本上你的数据类型 Expr 将是一个接口或抽象类,类型构造函数将是叶子: 因此,考虑到所有这些,一个简单的示例将是:

在haskell中:

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

【讨论】:

  • 谢谢,您给我的帮助超出了我的预期。我知道我遇到的问题是我没有选择与 Haskell 数据类型等效的正确模式,但是您还在答案中包含了使用 monad 的示例。
  • @Nemo 非常感谢,我一直很乐意提供帮助,这类问题是我的最爱。如果你有更多的配音,请不要犹豫给我写信
【解决方案2】:

其他答案涵盖了在 Java 中实现这一点的更惯用的方式,并描述了如何使用 Optional 来处理错误。但在这里,我想直接给出 Java 中 Haskell 模式匹配的等价物,即 visitor 模式:

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)

由于eval是递归的,你也可以用定点组合器写fix——然后在Java版本的ExprVisitor中使用this可能会变成更清楚:这就是您使eval 递归的方式!

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

【讨论】:

  • 这很容易适应使用 Optional 和 flatmap() 谢谢!
【解决方案3】:

您可以在除法之前同时评估x 和y:

Integer xE = x.evaluate(), yE = y.evaluate();

然后看看yE是否等于0:

if(yE == 0){
    // your logic here if it is a division by 0
}

这将为您提供以下功能:

public Integer evaluate() {        
    Integer xE = x.evaluate(), yE = y.evaluate();

    if(yE == 0){
        // your logic here if it is a division by 0
    }
    return xE / yE;  
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-03-19
    • 1970-01-01
    • 2021-08-14
    • 2013-05-17
    • 2017-08-04
    • 2012-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多