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.
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.
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.
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.
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.
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 .
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.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
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