Haskell: rzeczywista implementacja IO monad w innym języku?

W jaki sposób IO monad jest właściwie zaimplementowany?w sensie, jaka byłaby faktyczna implementacja funkcji main?

Jak nazwałbym funkcję Haskella (IO) z innego języka i czy w takim przypadku muszę utrzymywać IO moją jaźń?

Czy main pobiera akcje IO (leniwie) jako odniesienia, a następnie je wywołuje? Czy jest to praca tłumacza, kiedy znalazł działania w swoim stylu może je nazwać? A może coś innego?

Czy jest dobra implementacja IO monad w różnych język, który może pomóc w dogłębnym zrozumieniu tego, co dzieje się w głównej funkcji?

Edit:

Takie hGetContents bardzo mnie myli i sprawia, że nie jestem pewien, w jaki sposób IO jest naprawdę zaimplementowane.

Ok, powiedzmy, że mam bardzo prosty interpreter Haskell niestety nie ma wsparcia IO i dla ciekawości chcę dodać do niego te działania IO (unsafeIO triki również). Trudno to dostać od GHC, uścisków czy innych.

Author: Jonathan Cast, 2011-07-11

7 answers

Oto przykład jak można zaimplementować MONADĘ IO w Javie:

package so.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import static so.io.IOMonad.*;  
import static so.io.ConsoleIO.*;    

/**
 * This is a type containing no data -- corresponds to () in Haskell.
 */
class Unit {
    public final static Unit VALUE = new Unit(); 
}

/**
 * This type represents a function from A to R
 */
interface Function<A,R> {
    public R apply(A argument);
}

/**
 * This type represents an action, yielding type R
 */
interface IO<R> {
    /**
     * Warning! May have arbitrary side-effects!
     */
    R unsafePerformIO();
}

/**
 * This class, internally impure, provides pure interface for action sequencing (aka Monad)
 */
class IOMonad {
    static <T> IO<T> pure(final T value) {
        return new IO<T>() {
            @Override
            public T unsafePerformIO() {
                return value;
            }
        };
    }

    static <T> IO<T> join(final IO<IO<T>> action) {
        return new IO<T>(){
            @Override
            public T unsafePerformIO() {
                return action.unsafePerformIO().unsafePerformIO();
            }
        };
    }

    static <A,B> IO<B> fmap(final Function<A,B> func, final IO<A> action) {
        return new IO<B>(){
            @Override
            public B unsafePerformIO() {
                return func.apply(action.unsafePerformIO());
            }
        };
    }

    static <A,B> IO<B> bind(IO<A> action, Function<A, IO<B>> func) {
        return join(fmap(func, action));
    }
}

/**
 * This class, internally impure, provides pure interface for interaction with stdin and stdout
 */
class ConsoleIO {
    static IO<Unit> putStrLn(final String line) {
        return new IO<Unit>() {
            @Override
            public Unit unsafePerformIO() {
                System.out.println(line);
                return Unit.VALUE;
            }
        };
    };

    // Java does not have first-class functions, thus this:
    final static Function<String, IO<Unit>> putStrLn = new Function<String, IO<Unit>>() {
        @Override
        public IO<Unit> apply(String argument) {
            return putStrLn(argument);
        }
    };

    final static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

    static IO<String> getLine = new IO<String>() {
            @Override
            public String unsafePerformIO() {
                try {
                    return in.readLine();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        };
}

/**
 * The program composed out of IO actions in a purely functional manner.
 */
class Main {

    /**
     * A variant of bind, which discards the bound value.
     */
    static IO<Unit> bind_(final IO<Unit> a, final IO<Unit> b) {
        return bind(a, new Function<Unit, IO<Unit>>(){
            @Override
            public IO<Unit> apply(Unit argument) {
                return b;
            }
        });
    }

    /**
     * The greeting action -- asks the user for his name and then prints a greeting
     */
    final static IO<Unit> greet = 
            bind_(putStrLn("Enter your name:"), 
            bind(getLine, new Function<String, IO<Unit>>(){
                @Override
                public IO<Unit> apply(String argument) {
                    return putStrLn("Hello, " + argument + "!");
                }
            }));

    /**
     * A simple echo action -- reads a line, prints it back
     */
    final static IO<Unit> echo = bind(getLine, putStrLn);

    /**
     * A function taking some action and producing the same action run repeatedly forever (modulo stack overflow :D)
     */
    static IO<Unit> loop(final IO<Unit> action) {
        return bind(action, new Function<Unit, IO<Unit>>(){
            @Override
            public IO<Unit> apply(Unit argument) {
                return loop(action);
            }
        });
    }

    /**
     * The action corresponding to the whole program
     */
    final static IO<Unit> main = bind_(greet, bind_(putStrLn("Entering the echo loop."),loop(echo)));
}

/**
 * The runtime system, doing impure stuff to actually run our program.
 */
public class RTS {
    public static void main(String[] args) {
        Main.main.unsafePerformIO();
    }
}

Jest to runtime System implementujący interfejs do konsoli I / O wraz z małym czysto funkcjonalnym programem, który wita użytkownika, a następnie uruchamia pętlę echo.

Nie można zaimplementować niebezpiecznej części w Haskell, ponieważ Haskell jest językiem czysto funkcjonalnym. Jest on zawsze realizowany z obiektami niższego poziomu.

 24
Author: Rotsor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-26 02:07:02

Jeśli chcesz zrozumieć implementację IO monad, jest to bardzo dobrze opisane w nagradzanej pracy Phila Wadlera i Simona Peytona Jonesa, którzy byli tymi, którzy odkryli, jak używać monad do wprowadzania / wyjścia w czystym języku. Artykuł jest Imperative Functional Programming i znajduje się na stronach internetowych obu autorów.

 7
Author: Norman Ramsey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-14 00:41:06

W Javie 8 Lambda, możesz pobrać kod z powyższej odpowiedzi Rotsora, usunąć klasę Function, ponieważ Java 8 zapewnia interfejs funkcyjny z robi to samo i usunąć anonimową klasę cruft, aby uzyskać czystszy wygląd kodu w ten sposób:

package so.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.function.Function;

import static so.io.IOMonad.*;
import static so.io.ConsoleIO.*;

/**
 * This is a type containing no data -- corresponds to () in Haskell.
 */
class Unit {

   // -- Unit$

   public final static Unit VALUE = new Unit();

   private Unit() {
   }

}

/** This type represents an action, yielding type R */
@FunctionalInterface
interface IO<R> {

   /** Warning! May have arbitrary side-effects! */
   R unsafePerformIO();

}

/**
 * This, internally impure, provides pure interface for action sequencing (aka
 * Monad)
 */
interface IOMonad {

   // -- IOMonad$

   static <T> IO<T> pure(final T value) {
      return () -> value;
   }

   static <T> IO<T> join(final IO<IO<T>> action) {
      return () -> action.unsafePerformIO().unsafePerformIO();
   }

   static <A, B> IO<B> fmap(final Function<A, B> func, final IO<A> action) {
      return () -> func.apply(action.unsafePerformIO());
   }

   static <A, B> IO<B> bind(IO<A> action, Function<A, IO<B>> func) {
      return join(fmap(func, action));
   }

}

/**
 * This, internally impure, provides pure interface for interaction with stdin
 * and stdout
 */
interface ConsoleIO {

   // -- ConsoleIO$

   static IO<Unit> putStrLn(final String line) {
      return () -> {
         System.out.println(line);
         return Unit.VALUE;
      };
   };

   final static Function<String, IO<Unit>> putStrLn = arg -> putStrLn(arg);

   final static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

   static IO<String> getLine = () -> {
      try {
         return in.readLine();
      }

      catch (IOException e) {
         throw new RuntimeException(e);
      }
   };

}

/** The program composed out of IO actions in a purely functional manner. */
interface Main {

   // -- Main$

   /** A variant of bind, which discards the bound value. */
   static IO<Unit> bind_(final IO<Unit> a, final IO<Unit> b) {
      return bind(a, arg -> b);
   }

   /**
    * The greeting action -- asks the user for his name and then prints 
    * greeting
    */
   final static IO<Unit> greet = bind_(putStrLn("Enter your name:"),
         bind(getLine, arg -> putStrLn("Hello, " + arg + "!")));

   /** A simple echo action -- reads a line, prints it back */
   final static IO<Unit> echo = bind(getLine, putStrLn);

   /**
    * A function taking some action and producing the same action run repeatedly
    * forever (modulo stack overflow :D)
    */
   static IO<Unit> loop(final IO<Unit> action) {
      return bind(action, arg -> loop(action));
   }

    /** The action corresponding to the whole program */
    final static IO<Unit> main = bind_(greet, bind_(putStrLn("Entering the echo loop."), loop(echo)));

}

/** The runtime system, doing impure stuff to actually run our program. */
public interface RTS {

    // -- RTS$

    public static void main(String[] args) {
       Main.main.unsafePerformIO();
    }

 }

Zauważ, że zmieniłem również metody statyczne deklarowane przez klasy na metody statyczne deklarowane przez interfejs. Dlaczego? Bez konkretnego powodu, tylko, że można w Java 8.

 7
Author: Martian Odyssey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-07 21:40:38

Monada IO jest w zasadzie zaimplementowana jako transformator stanu (podobny do State), ze specjalnym tokenem RealWorld. Każda operacja IO zależy od tego tokena i przekazuje go po jej zakończeniu. unsafeInterleaveIO wprowadza drugi token, aby nowa operacja IO mogła się rozpocząć, podczas gdy druga nadal wykonuje swoją pracę.

Zazwyczaj nie musisz dbać o implementację. Jeśli chcesz wywołać IO-functions z innych języków, GHC dba o usunięcie wrappera IO. Rozważ to małe fragment:

printInt :: Int -> IO ()
printInt int = do putStr "The argument is: "
                  print int

foreign export ccall printInt :: Int -> IO ()

To generuje symbol do wywołania printInt z C. funkcja staje się:

extern void printInt(HsInt a1);

Gdzie HsInt jest po prostu (w zależności od platformy) typedef d int. Więc widzicie, monada IO została całkowicie usunięta.

 5
Author: fuz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-11 15:26:08

Kwestię implementacji IO zostawię innym ludziom, którzy wiedzą nieco więcej. (Chociaż zaznaczę, jak jestem pewien, że będą również, że prawdziwe pytanie nie jest " jak jest IO zaimplementowane w Haskell?"ale raczej" w jaki sposób IO jest zaimplementowane w GHC?"lub" w jaki sposób IO jest zaimplementowane w Hugs?", itp. Wyobrażam sobie, że implementacje bardzo się różnią.) Jednak to pytanie:

Jak wywołać funkcję haskell (IO) z innego języka i czy w takim przypadku muszę utrzymywać IO mój siebie?

... znajduje się w specyfikacji FFI .

 2
Author: Daniel Wagner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-11 21:48:12

Poniżej znajduje się aktualna implementacja IO w GHC 7.10.

Typ IO jest zasadniczo monadą stanu w typie State# RealWorld (zdefiniowane w GHC.Types):

{- |
A value of type @'IO' a@ is a computation which, when performed,
does some I\/O before returning a value of type @a@.
There is really only one way to \"perform\" an I\/O action: bind it to
@Main.main@ in your program.  When your program is run, the I\/O will
be performed.  It isn't possible to perform I\/O from an arbitrary
function, unless that function is itself in the 'IO' monad and called
at some point, directly or indirectly, from @Main.main@.
'IO' is a monad, so 'IO' actions can be combined using either the do-notation
or the '>>' and '>>=' operations from the 'Monad' class.
-}
newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #))

Monada IO jest ścisła, ponieważ bindIO jest zdefiniowana przez case matching (zdefiniowane w GHC.Base):

instance  Monad IO  where
    {-# INLINE return #-}
    {-# INLINE (>>)   #-}
    {-# INLINE (>>=)  #-}
    m >> k    = m >>= \ _ -> k
    return    = returnIO
    (>>=)     = bindIO
    fail s    = failIO s

returnIO :: a -> IO a
returnIO x = IO $ \ s -> (# s, x #)

bindIO :: IO a -> (a -> IO b) -> IO b
bindIO (IO m) k = IO $ \ s -> case m s of (# new_s, a #) -> unIO (k a) new_s
Ta implementacja jest omówiona w blogu Edwarda Yang.
 2
Author: ntc2,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-13 19:11:01

W rzeczywistości "IO a" to po prostu "() -> a " w języku nieczystym (gdzie funkcje mogą mieć efekt uboczny). Załóżmy, że chcesz zaimplementować IO w SML:

structure Io : MONAD =
struct
  type 'a t = unit -> 'a
  return x = fn () => x
  fun (ma >>= g) () = let a = ma ()
                      in g a ()
  executeIo ma = ma ()
end
 1
Author: ysdx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-15 11:37:07