Haskell预monadic I / O

我想知道在IO monad还没有发明的时候,如何在Haskell中完成I / O。 有人知道一个例子。

编辑:如果没有现代Haskell中的IO Monad,我能完成吗? 我更喜欢与现代GHC一起工作的例子。

在引入IO monad之前, main是一个types为[Response] -> [Request]的函数。 一个Request将表示一个I / O操作,如写入一个通道或文件,或读取input,或读取环境variables等。 Response将是这样一个行动的结果。 例如,如果执行ReadChanReadFile请求,则相应的Response将是Str str ,其中str将是包含读取input的String 。 执行AppendChanAppendFileWriteFile请求时,响应将简单地为Success 。 (当然,假设在所有情况下,给定的行动实际上是成功的)。

因此,一个Haskell程序可以通过build立一个Request值列表并从给出的main列表中读取相应的响应来工作。 例如,从用户那里读取一个数字的程序可能看起来像这样(为了简单起见,不考虑任何error handling):

 main :: [Response] -> [Request] main responses = [ AppendChan "stdout" "Please enter a Number\n", ReadChan "stdin", AppendChan "stdout" . show $ enteredNumber * 2 ] where (Str input) = responses !! 1 firstLine = head . lines $ input enteredNumber = read firstLine 

正如Stephen Tetley在评论中已经指出的那样,这个模型的详细规范在1.2 Haskell报告的第7章中给出。


如果没有现代Haskell中的IO Monad,我能完成吗?

不。Haskell不再支持直接执行IO的Response / Request方式,而main的types是IO () ,所以你不能写一个不涉及IO的Haskell程序,即使你可以仍然没有办法做任何I / O。

但是你可以做的是编写一个函数,该函数采用旧式的主函数并将其转换为IO操作。 然后你可以使用旧的风格来写所有的东西,然后只在main地方使用IO,在那里你只需要调用真正的主函数的转换函数。 这样做几乎肯定会比使用IO monad更麻烦(而且会混淆现代Haskeller阅读你的代码),所以我绝对不会推荐它。 但是这可能的。 这样的转换函数可能看起来像这样:

 import System.IO.Unsafe -- Since the Request and Response types no longer exist, we have to redefine -- them here ourselves. To support more I/O operations, we'd need to expand -- these types data Request = ReadChan String | AppendChan String String data Response = Success | Str String deriving Show -- Execute a request using the IO monad and return the corresponding Response. executeRequest :: Request -> IO Response executeRequest (AppendChan "stdout" message) = do putStr message return Success executeRequest (AppendChan chan _) = error ("Output channel " ++ chan ++ " not supported") executeRequest (ReadChan "stdin") = do input <- getContents return $ Str input executeRequest (ReadChan chan) = error ("Input channel " ++ chan ++ " not supported") -- Take an old style main function and turn it into an IO action executeOldStyleMain :: ([Response] -> [Request]) -> IO () executeOldStyleMain oldStyleMain = do -- I'm really sorry for this. -- I don't think it is possible to write this function without unsafePerformIO let responses = map (unsafePerformIO . executeRequest) . oldStyleMain $ responses -- Make sure that all responses are evaluated (so that the I/O actually takes -- place) and then return () foldr seq (return ()) responses 

然后你可以像这样使用这个函数:

 -- In an old-style Haskell application to double a number, this would be the -- main function doubleUserInput :: [Response] -> [Request] doubleUserInput responses = [ AppendChan "stdout" "Please enter a Number\n", ReadChan "stdin", AppendChan "stdout" . show $ enteredNumber * 2 ] where (Str input) = responses !! 1 firstLine = head . lines $ input enteredNumber = read firstLine main :: IO () main = executeOldStyleMain doubleUserInput 

@ sepp2k已经澄清了这是如何工作的,但我想添加几个字,akhem,几行代码。

我真的很抱歉 没有unsafePerformIO,我不认为有可能写这个函数

当然可以! 你几乎不应该使用unsafePerformIO http://chrisdone.com/posts/haskellers

我使用稍微不同的Requesttypes的构造函数,所以它不需要通道版本( stdin / stdout像在@ sepp2k的代码)。 这是我的解决scheme:

(注意: getFirstReq在空列表上不起作用,你必须为此添加一个案例,它应该是微不足道的)

 data Request = Readline | PutStrLn String data Response = Success | Str String type Dialog = [Response] -> [Request] execRequest :: Request -> IO Response execRequest Readline = getLine >>= \s -> return (Str s) execRequest (PutStrLn s) = putStrLn s >> return Success dialogToIOMonad :: Dialog -> IO () dialogToIOMonad dialog = let getFirstReq :: Dialog -> Request getFirstReq dialog = let (req:_) = dialog [] in req getTailReqs :: Dialog -> Response -> Dialog getTailReqs dialog resp = \resps -> let (_:reqs) = dialog (resp:resps) in reqs in do let req = getFirstReq dialog resp <- execRequest req dialogToIOMonad (getTailReqs dialog resp)