这是我的第一次尝试:
import java.util.function.*;
import java.util.ArrayList;
public class IO<A> {
private Function<World,Tuple<World,A>> transform;
private class World {
private ArrayList<String> stdin;
private ArrayList<String> stdout;
public World() {
this.stdin = new ArrayList<String>();
this.stdout = new ArrayList<String>();
}
}
private class Tuple<F,S> {
public F fst;
public S snd;
public Tuple(F fst, S snd) {
this.fst = fst;
this.snd = snd;
}
}
public IO(Function<World,Tuple<World,A>> transform) {
this.transform = transform;
}
public IO<A> pure(A a) {
return new IO<A>(r -> new Tuple<World,A>(r,a));
}
public <B> IO<B> bind(IO<A> io, Function<A,IO<B>> f) {
return new IO<B>(r -> {
Tuple<World,A> result = io.transform.apply(r);
IO<B> ioB = f.apply(result.snd);
return ioB.transform.apply(result.fst);
});
}
}
但是当我尝试编译时,我收到以下错误:
IO.java:29: error: incompatible types: IO<B>.World cannot be converted to IO<A>.World
Tuple<World,A> result = io.transform.apply(r);
^
where B,A are type-variables:
B extends Object declared in method <B>bind(IO<A>,Function<A,IO<B>>)
A extends Object declared in class IO
我不明白世界级与类型变量没有关系,但javac认为它确实如此。我做错了什么?
答案 0 :(得分:4)
不考虑在Java中复制Haskell IO类型的方法的保真度:
编译器认为A
方法签名中的bind
与类定义中的A
相同。你告诉过我们这不是用语言。为了将它传达给编译器,您需要将事物设置为静态并引入几个方法级类型参数:
import java.util.function.*;
import java.util.ArrayList;
public class IO<A> {
private Function<World,Tuple<World,A>> transform;
private static class World {
private ArrayList<String> stdin;
private ArrayList<String> stdout;
public World() {
this.stdin = new ArrayList<String>();
this.stdout = new ArrayList<String>();
}
}
private static class Tuple<F,S> {
public F fst;
public S snd;
public Tuple(F fst, S snd) {
this.fst = fst;
this.snd = snd;
}
}
private IO(Function<World,Tuple<World,A>> transform) {
this.transform = transform;
}
public static <A> IO<A> pure(A a) {
return new IO<A>(r -> new Tuple<World,A>(r,a));
}
public static <A,B> IO<B> bind(IO<A> io, Function<A,IO<B>> f) {
return new IO<B>(r -> {
Tuple<World,A> result = io.transform.apply(r);
IO<B> ioB = f.apply(result.snd);
return ioB.transform.apply(result.fst);
});
}
}
答案 1 :(得分:2)
我认为&#34;操作monad&#34;方法更适合解释Haskell I / O的本质。 Haskell版本非常简单:
data PrimOp a where
PutStr :: String -> PrimOp ()
GetLine :: PrimOp String
-- Whatever other primitives you want
data MyIO a where
Pure :: a -> MyIO a
Bind :: !(MyIO a) -> (a -> MyIO b) -> MyIO b
LiftPrim :: !(PrimOp a) -> MyIO a
instance Functor MyIO where
fmap = liftM
instance Applicative MyIO where
pure = Pure
(<*>) = ap
instance Monad MyIO where
(>>=) = Bind
MyIO
值并不是一些神奇的世界传递函数;他们只是简单的数据。如果我们愿意,我们可以解释数据以实际执行所表示的操作:
runPrimOp :: PrimOp a -> IO a
runPrimOp (PutStr s) = putStr s
runPrimOp GetLine = getLine
runMyIO :: MyIO a -> IO a
runMyIO (Pure a) = pure a
runMyIO (Bind m f) = runMyIO m >>= runMyIO . f
runMyIO (LiftPrim prim) = runPrimOp prim
实际上可以编写一个Haskell编译器,其IO
类型看起来很像MyIO
,并且其运行时系统直接解释该类型的值。
我尝试过下面的Java翻译。我从来没有真正成为一名Java程序员,而且自从我使用它以来已经很长时间了,所以这可能非常不合时宜甚至是错误的。我想你可能想要使用某些版本的&#34;访客模式&#34;代表Bind
和Pure
的解释(将事物带入像Control.Monad.Operational
这样的一般事物的领域),同时对run
子类使用PrimOp
方法IO
。由于我不太了解正确的Java方式,所以我试图保持简单。
public interface IO <A> {
public A run ();
}
public final class Pure <A> implements IO <A> {
private final A val;
Pure (A x) { val = x; }
public A run () {
return val;
}
}
棘手的位是Bind
,需要存在量化。我不知道在Java中这样做的惯用方法,所以我做了一些似乎有用的尴尬。也就是说,我编写了一个辅助类OpenBind
,它暴露了两个类型变量,然后是一个包含Bind
的类OpenBind
,使其中一个变量变得狂野。
import java.util.function.Function;
public final class Bind <A> implements IO <A> {
private final OpenBind <?,A> ob;
public <B> Bind (IO <B> m, Function <B,IO <A>> f) {
ob = new OpenBind <B,A> (m, f);
}
public A run() {
return (ob.run());
}
private final static class OpenBind <Fst,Snd> {
private final IO <Fst> start;
private final Function <Fst, IO <Snd>> cont;
public OpenBind (IO <Fst> m, Function <Fst, IO <Snd>> f) {
start = m;
cont = f;
}
public final Snd run () {
Fst x = start.run();
IO <Snd> c = cont.apply(x);
return (c.run());
}
}
}
primops本身很简单(我找不到Java等价的()
所以我自己写了Unit
):
public class PutStr implements IO <Unit> {
private String str;
public PutStr (String s) {
str = s;
}
public Unit run () {
System.out.print(str);
return Unit.unit;
}
}
public final class Unit {
private Unit () {}
public static final Unit unit = new Unit ();
}
public class GetLine implements IO <String> {
private GetLine () {}
public static final GetLine getLine = new GetLine ();
public String run () {
// Replace the next line with whatever you actually use to
// read a string.
return "";
}
}