2016-11-12 79 views
5

這是我第一次嘗試:如何在Java中實現Haskell的IO類型?

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

我不太清楚你的目標是有什麼用,但我認爲你錯誤地認爲'世界'的目的,這不是考慮'IO'單子的最佳方式。無論哪種方式,您定義的「世界」意味着「變換」可以添加到輸入並從輸出中移除,這可能是不希望的行爲。 –

+0

目標是向Java開發人員介紹Haskell。我認爲純粹的IO是Haskell最酷的概念,所以我只是製作一個簡單的IO模型。我同意,雖然標準清單應該只讀。 –

+0

我不是Java開發人員,但我不明白這麼毛茸茸的東西可能是介紹Haskell的'IO'類型的好方法。爲此,我會提出一種「操作monad」方法,其中'IO a'被視爲由原始命令和延續組成的數據結構。 – dfeuer

回答

4

撇開你的方法的保真度在Java中複製Haskell的IO類型:

編譯器認爲,在bind方法的簽名A相同的類定義的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); 
     }); 
    } 
} 
+0

啊我試過了,但我沒有使靜態的方法,謝謝! –

+0

把'bind'作爲一個非靜態方法,只需刪除第一個參數並用'this'替換它就可以了,並且可以說更好。 –

+0

@DerekElkins你可能是對的,但現在我剛剛添加了一個免責聲明。我認爲有更多的問題,而不僅僅是... –

2

我認爲一個「業務單子」的做法是解釋哈斯克爾我的精髓更好/ 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程序員,並且自從我使用它以來已經很長時間了,所以這可能是非常單一的,甚至是錯誤的。我想你可能會想使用某種版本的「訪問者模式」來表示解釋BindPure(將事物帶入一般東西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,其中一個變量保持爲wild。

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 ""; 
    } 
} 
+0

我不清楚如何將GADTs翻譯成Java數據類型,但這是一個更好的解決方案。 –

+0

@KyleMcKean,我已經添加了一個翻譯到Java,有一些注意事項。 – dfeuer

+0

來自另一個非Java人的一些說明:有一個重複的導入語句。 OpenBind可以是一個靜態嵌套類來限制其可見性。請提供一個如何使用它的例子。 –