简体   繁体   English

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

[英]What is the Java equivalent of this Haskell function?

Philip Wadler's paper "Monads for functional programming" has an example of a function eval which performs division written in Haskell. Philip Wadler 的论文“Monads for Functional Programming”有一个函数 eval 的例子,它执行用 Haskell 编写的除法。

Here it is as adapted in "Programming in Haskell" by Graham Hutton:这是 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

My Java equivalent of this is:我的 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());
    }
}

This seems fine but how do I develop this code so that I can demonstrate a Try monad (to catch division by zero) in Java?这看起来不错,但是如何开发此代码以便我可以在 Java 中演示 Try monad(以捕捉除以零)?

Edit: a workaround to failures in this kind of cases is the use of the Maybe Monad , and his cousin in Java is the Optional class, where Option.of would be return and flatMap would be bind .编辑:在这种情况下失败的解决方法是使用Maybe Monad ,他在 Java 中的堂兄是Optional类,其中Option.ofreturnflatMap将是bind On the other hand in Java and others OO languages there is a common pattern to use in this kind of cases called composite, basically your data type Expr will be an interface or abstract class, and the type constructors will be the leaves: So, with all of that in mind, a simple example working would be:另一方面,在 Java 和其他 OO 语言中,在这种称为复合的情况下,有一种通用模式可以使用,基本上你的数据类型Expr将是一个接口或抽象类,而类型构造函数将是叶子:所以,用考虑到所有这些,一个简单的工作示例是:

In 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)

Equivalent approach in Java: Java中的等效方法:

import java.util.Optional;

public interface Expr {

    public Optional<Integer> eval();

}

Then the leaves implementing Expr:然后叶子实现 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;
    }
}

Then the recursive case:然后递归的情况:

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());

    }
}

The main function output will be:主要功能输出将是:

Optional[1]
Optional.empty

Other answers have covered a more idiomatic way of implementing this in Java, and described how to use Optional to handle errors.其他答案涵盖了在 Java 中实现这一点的更惯用的方法,并描述了如何使用Optional来处理错误。 But here I'd like to give the direct equivalent of Haskell pattern matching in Java, with the visitor pattern :但在这里我想给出 Java 中 Haskell 模式匹配的直接等价物,使用访问者模式

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);
}

In the land of functional programming, this is called Böhm–Berarducci encoding—sometimes referred to as Church encoding, although they're different things.在函数式编程领域,这称为 Böhm–Berarducci 编码——有时也称为 Church 编码,尽管它们是不同的东西。 This is a fancy-sounding way of saying “representing data types and pattern matching with functions”.这是“用函数表示数据类型和模式匹配”的一种听起来很花哨的方式。 You can of course use this encoding of matching in Haskell:您当然可以在 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)

Since eval is recursive, you can also write it using the fixed point combinator fix —and then the use of this in the ExprVisitor in the Java version may become more clear: it's how you make eval recursive!由于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)

And here's the other half of the encoding: we can do away with the data type altogether and just use functions:这是编码的另一半:我们可以完全取消数据类型,只使用函数:

{-# 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

And that implementation of eval can of course be written as just this: eval实现当然可以写成这样:

eval = visit expr id div

You could evaluate both x and y before the division:您可以在除法之前评估xy

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

And then see if yE is equal to 0 :然后查看yE是否等于0

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

Which would yield you with the following function:这将为您提供以下功能:

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;  
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM