IO monad实际上是如何实现的?从main
函数的实际实现方式来看是什么意思?
我如何从另一种语言调用haskell函数(IO),在这种情况下我是否需要维护IO?
main
是否将IO操作(Lazily)作为引用拉出来然后调用它们?
或者它是解释工作,当它发现它可以调用它们的方式的行动?
或者别的什么?
有不同语言的IO monad实现是否有助于深入理解主要功能中发生的事情?
编辑:
这样的hGetContents
让我很困惑,让我不确定IO是如何真正实现的。
好吧,假设我有一个非常简单的纯Haskell解释器,不幸的是没有IO支持,为了好奇,我想向它添加这个IO动作(unsafeIO
技巧)。很难从GHC,Hugs或其他人那里得到它。
答案 0 :(得分:24)
以下是如何在Java中实现IO monad的示例:
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();
}
}
这是一个运行时系统,它实现了控制台I / O的接口以及一个小的纯功能程序,该程序向用户打招呼,然后运行一个echo循环。
我无法在Haskell中实现不安全的部分,因为Haskell是纯粹的函数式语言。它总是在较低级别的设施中实施。
答案 1 :(得分:7)
如果你想了解IO monad的实现,那么在Phil Wadler和Simon Peyton Jones的获奖论文中对它进行了很好的描述,他们发现了如何使用monad进行输入/输出的人。一种纯粹的语言。该论文是Imperative Functional Programming,位于两个作者的网站上。
答案 2 :(得分:7)
使用Java 8 Lambdas,您可以从Rotsor上面的答案中获取代码,删除Function类,因为Java 8提供了一个与FunctionalInterface相同的功能,并删除了匿名类cruft以实现更清晰的代码,如下所示:
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();
}
}
请注意,我还将类声明的静态方法更改为接口声明的静态方法。为什么?没有特别的原因,只是你可以在Java 8中使用。
答案 3 :(得分:5)
IO
monad基本上实现为状态转换器(类似于State
),带有特殊标记RealWorld
。每个IO操作都依赖于此令牌,并在完成时传递它。 unsafeInterleaveIO
引入了第二个令牌,以便可以启动新的IO操作,而另一个仍然可以正常工作。
通常,您不必关心实施。如果要从其他语言调用IO函数,GHC会关心删除IO包装器。考虑一下这个小片段:
printInt :: Int -> IO ()
printInt int = do putStr "The argument is: "
print int
foreign export ccall printInt :: Int -> IO ()
这将生成一个从C调用printInt
的符号。函数变为:
extern void printInt(HsInt a1);
HsInt
只是一个(取决于您的平台)typedef
d int
。所以你看,monad IO
已被完全删除。
答案 4 :(得分:3)
IO
的实际实现。 IO
类型本质上是State# RealWorld
(defined in GHC.Types
)类型的状态monad:
{- |
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 #))
IO
monad 严格,因为bindIO
由case
匹配(defined in 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
此实现在a blog post by Edward Yang中讨论。
答案 5 :(得分:2)
我会将实施IO的问题留给其他知道更多的人。 (虽然我会指出,我相信他们也会这样,真正的问题不是“如何在Haskell中实现IO?”而是“如何在GHC中实现IO?”或“如何实现IO”在Hugs?“等我认为实现方式差别很大。)但是,这个问题:
如何从另一种语言调用haskell函数(IO),在这种情况下我是否需要维护IO?
...在FFI specification中得到了深入的回答。
答案 6 :(得分:1)
事实上,“IO a”只是“() - &gt; a”用不纯的语言(其中功能可能有副作用)。假设你想在SML中实现IO:
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
答案 7 :(得分:0)
注意:我对 Clean 的经验很少 - 考虑一下自己的警告!
基于 System.IO,使用 F. Warren Burton 在 variation 中描述的方法的 Nondeterminism with Referential Transparency in Functional Programming Languages:
definition module System.Alt.IO
from Control.Applicative import class pure, class <*>, class Applicative
from Data.Functor import class Functor
from Control.Monad import class Monad
from StdOverloaded import class toString
from System._OI import OI
:: IO a = IO .(*OI -> a)
execIO :: !(IO a) !*World -> *World
evalIO :: !(IO a) !*World -> *(a, !*World)
withOI :: (*OI -> .a) -> IO .a
putStr :: String -> IO ()
putStrLn :: String -> IO ()
print :: a -> IO () | toString a
getChar :: IO Char
getLine :: IO String
readFileM :: !String -> IO String
writeFileM :: !String !String -> IO ()
instance Functor IO
instance pure IO
instance <*> IO
instance Monad IO
unsafePerformIO :: !(*OI -> .a) -> .a
unsafePerformIOTrue :: !(*OI -> a) -> Bool
implementation module System.Alt.IO
import StdFile
from StdFunc import o, id
import StdMisc
import StdString
import System._OI
import Control.Applicative
import Control.Monad
import Data.Functor
from Text import class Text (trim), instance Text String
execIO :: !(IO a) !*World -> *World
execIO (IO g) world
# (u, world) = newOI world
#! x = g u
= world
evalIO :: !(IO a) !*World -> *(a, !*World)
evalIO (IO g) world
# (u, world) = newOI world
#! x = g u
= (x, world)
withWorld :: (*World -> *(.a, *World)) -> IO .a
withWorld f = IO g
where
g u
# (x, world) = f (getWorld u)
= from_world "withWorld" x world
instance Functor IO
where
fmap f x = x >>= (lift o f)
instance pure IO
where
pure x = IO (\u -> case partOI u of (_, _) = x)
instance <*> IO
where
(<*>) f g = liftA2 id f g
instance Monad IO where
bind ma a2mb = IO (run ma)
where
run (IO g) u
# (u1, u2) = partOI u
#! x = g u1
# (IO k) = a2mb x
= k u2
putStr :: String -> IO ()
putStr str = withWorld f
where
f world
# (out, world) = stdio world
# out = fwrites str out
# (_, world) = fclose out world
= ((), world)
putStrLn :: String -> IO ()
putStrLn str = putStr (str +++ "\n")
print :: a -> IO () | toString a
print x = putStrLn (toString x)
getChar :: IO Char
getChar = withWorld f
where
f world
# (input, world) = stdio world
# (ok, c, input) = freadc input
# (_, world) = fclose input world
= (c, world)
getLine :: IO String
getLine = withWorld f
where
f world
# (input, world) = stdio world
# (str, input) = freadline input
# (_, world) = fclose input world
= (trim str, world)
readFileM :: !String -> IO String
readFileM name = withWorld f
where
f world
# (ok, file, world) = fopen name FReadText world
# (str, file) = freads file 16777216
# (ok, world) = fclose file world
= (str, world)
writeFileM :: !String !String -> IO ()
writeFileM name txt = withWorld f
where
f world
# (ok, file, world) = fopen name FWriteText world
# file = fwrites txt file
# (ok, world) = fclose file world
= ((), world)
unsafePerformIO :: !(*OI -> .a) -> .a
unsafePerformIO f
#! x = f make_oi
= x
unsafePerformIOTrue :: !(*OI -> a) -> Bool
unsafePerformIOTrue f
#! x = f make_oi
= True
make_oi
# (u, w) = newOI make_world
= from_world "make_oi" u w
from_world :: String a !*World -> a
from_world name x world
| world_to_true world = x
| otherwise = abort ("error in +++ name +++ "\n")
world_to_true :: !*World -> Bool
world_to_true world
= code inline {
pop_a 1
pushB TRUE
}
make_world
= code {
fillI 65536 0
}
definition module System._OI
from StdFunc import id
:: OI
partOI :: *OI -> *(*OI, *OI)
newOI :: *World -> *(*OI, *World)
getWorld :: *OI -> *World
implementation module System._OI
:: OI = OI
partOI :: !*OI -> *(*OI, *OI) // NOTE - this may need
partOI u // to be defined with
# u1 = OI // ABC instructions
# u2 = id OI
= (u1, u2)
newOI :: !*World -> *(*OI, *World) // ...or: Start :: *OI -> ()
newOI world
= (OI, world)
getWorld :: !*OI -> *World // only needed because I/O
getWorld OI // operations in Clean 3.0
= code inline { fillA 65536 0 } // rely on World values