2016-11-12 19 views
5

Bu benim ilk girişimi:Haskell'in IO tipini Java'da nasıl uygularsınız?

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

Ama aşağıdaki hatayı alıyorum bu derlemeye çalıştığınızda:

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 

Ne anlamıyorum dünya sınıfı için hiçbir ilişkisi yoktur olduğu type variable ama javac yaptığı düşünüyor. Neyi yanlış yapıyorum?

+0

Ben hedef bununla ne tam olarak emin değilim ama bence siz 'Dünya'nın amacını karıştırıyorsunuz ve bu' IO 'monadını düşünmenin en iyi yolu değil.Her iki durumda da, "Dünya" tanımınız, "dönüşümlerin" girdiye ekleyebileceğini ve muhtemelen istenmeyen davranış olan çıktıdan çıkarılabileceğini ima eder. –

+0

Amaç, java geliştiricilerine Haskell'i tanıtmaktır. Bence saf IO Haskell havalı konseptidir, bu yüzden basit bir IO modelini yapıyorum. Stdin listesinin salt okunur olmasına razıyım. –

+0

Ben bir Java geliştiricisi değilim, ama bu kadar kıllı bir şeyin Haskell'in 'IO 'tipini tanıtmanın iyi bir yolu olabileceğini görmüyorum. Bunun için, “IO a” nın ilkel komutlar ve sürekliliklerden oluşan bir veri yapısı olarak görüldüğü bir “operasyonel monad” yaklaşımı öneririm. – dfeuer

cevap

4

Java Haskell'ın IO türünü kopyalayan için yaklaşımın sadakat kenara bırakırsak:

derleyici sizin bind yöntemin imzası A sınıf tanımında A aynı olduğunu düşünür. Bize sözlerin olmadığını söylemiştin.

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

Ah Denedim ama yöntemleri statik yapmadım, teşekkürler! –

+0

“bind” in statik olmayan bir yöntem olarak kalması ve sadece ilk argümanı kaldırması ve bunun yerine “this” ile değiştirilmesi iyi olurdu. –

+0

@DerekElkins muhtemelen haklısınız, ancak şimdilik bir feragatname ekledim. Bence bundan daha fazla sorun var ... –

2

Bir "operasyonel monad" yaklaşımı Haskell I özünü açıklayan için daha iyi olduğunu düşünüyorum: derleyici o iletişim kurmak için, statik şeyler yapmak ve yöntem düzeyinde tipi parametrelerin devreye soktuk gerekiyor /O. Haskell versiyonu oldukça basit olabilir:

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 değerleri bazı büyülü dünya geçen fonksiyonlar değildir; Onlar sadece düz bir veri.

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 

Aslında kimin IO tip bir Haskell derleyicisi yazmak mümkün olacağını kimin çalıştırma sistemi bu değerlerini yorumladığı MyIO çok benziyor ve: Biz bunu arzu aslında eylem temsil gerçekleştirmek için verileri yorumlayabilir doğrudan yazın.


Aşağıdaki Java'ya bir çeviri girişiminde bulundum. Hiç bir zaman bir Java programcısı olmadım ve hiç kullanılmadığımdan çok uzun zaman geçti, bu yüzden bu çok tekdüze veya hatta yanlış olabilir. Bind ve Pure (Control.Monad.Operational gibi genel bir şeyin alemine getirilmesi), PrimOpIO alt sınıfı için run yöntemini kullanırken, "ziyaretçi kalıbı" nın bazı sürümlerini kullanmak istediğinizi tahmin ediyorum. Gerçekten doğru Java yolunu bilmediğimden, bunu basit tutmaya çalıştım.

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

zor biraz varoluş ölçümü ihtiyacı Bind vardır. Java'da bunu yapmak için deyimsel yolu bilmiyorum, bu yüzden işe yaramış gibi görünen garip bir şey yaptım. Yani, iki tip değişkenini ve sınıfını bir OpenBind bu değişkenlerden birini siler bırakan bir sınıf OpenBind yazdı.

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 kendilerini oldukça basit (ben çok yazdım () bir Java eşdeğer bulamadı benim kendi 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

GADT'leri java veri türlerine nasıl çevireceğimi açıklamıyorum ama bu çok daha iyi bir çözüm. –

+0

@KyleMcKean, Java'ya bazı uyarılar ekleyerek bir çeviri ekledim. – dfeuer

+0

Java dışındaki bir başka kullanıcıdan bazı notlar: Yinelenen bir içe aktarma bildirimi var. OpenBind, görünürlüğünü sınırlamak için statik bir yuvalanmış sınıf olabilir. Lütfen bunun nasıl kullanılacağına dair bir örnek verin. –