I think the “operational monad” approach better explains the essence of Haskell I / O. The Haskell version can be quite simple:
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 values MyIO not some magical world-transmitting functions; it's just data. We can interpret the data to actually perform the presented action if we want to:
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 mf) = runMyIO m >>= runMyIO . f runMyIO (LiftPrim prim) = runPrimOp prim
In fact, you could write a Haskell compiler whose type IO looks just like MyIO , and the runtime system directly interprets the values of this type.
I tried to do a Java translation below. I have never been a Java programmer, and I have been using it for a long time, so this can be terribly uniomatic or even wrong. I suggest that you probably want to use some version of the “visitor pattern” to represent Bind and Pure interpretations (translating things into something common, like Control.Monad.Operational ) using the run method to subclass PrimOp IO . Since I really don't know the correct Java method, I tried to just keep it simple.
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; } }
Hard Bind bit that requires existential quantification. I don't know the idiomatic way to do this in Java, so I did something awkward that seems to work. Namely, I wrote a helper class, OpenBind , which provides two type variables, and then a Bind class, which completes OpenBind , leaving one of these wild variables.
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()); } } }
The primitives themselves are pretty simple (I could not find the Java () equivalent, so I wrote my own 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 () {