[英]What is a good way to implement the equivalent of Haskell's “or” function using Streams in 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.of
將return
而flatMap
將是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
您可以在除法之前評估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;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.