Haskell:实际的IO monad实现,用不同的语言?

时间:2011-07-11 09:00:45

标签: haskell io interpreter monads

IO monad实际上是如何实现的?从main函数的实际实现方式来看是什么意思?

我如何从另一种语言调用haskell函数(IO),在这种情况下我是否需要维护IO?

main是否将IO操作(Lazily)作为引用拉出来然后调用它们? 或者它是解释工作,当它发现它可以调用它们的方式的行动? 或者别的什么?

有不同语言的IO monad实现是否有助于深入理解主要功能中发生的事情?

编辑:

这样的hGetContents让我很困惑,让我不确定IO是如何真正实现的。

好吧,假设我有一个非常简单的纯Haskell解释器,不幸的是没有IO支持,为了好奇,我想向它添加这个IO动作(unsafeIO技巧)。很难从GHC,Hugs或其他人那里得到它。

8 个答案:

答案 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)

以下是GHC 7.10中IO的实际实现。

IO类型本质上是State# RealWorlddefined 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 严格,因为bindIOcase匹配(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