簡體   English   中英

這個 Haskell 函數的 Java 等價物是什么?

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

Philip Wadler 的論文“Monads for Functional Programming”有一個函數 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(以捕捉除以零)?

編輯:在這種情況下失敗的解決方法是使用Maybe Monad ,他在 Java 中的堂兄是Optional類,其中Option.ofreturnflatMap將是bind 另一方面,在 Java 和其他 OO 語言中,在這種稱為復合的情況下,有一種通用模式可以使用,基本上你的數據類型Expr將是一個接口或抽象類,而類型構造函數將是葉子:所以,用考慮到所有這些,一個簡單的工作示例是:

在哈斯克爾:

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

其他答案涵蓋了在 Java 中實現這一點的更慣用的方法,並描述了如何使用Optional來處理錯誤。 但在這里我想給出 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);
}

在函數式編程領域,這稱為 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

您可以在除法之前評估xy

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM