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
将是这样一个行动的结果。 例如,如果执行ReadChan
或ReadFile
请求,则相应的Response
将是Str str
,其中str
将是包含读取input的String
。 执行AppendChan
, AppendFile
或WriteFile
请求时,响应将简单地为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
我使用稍微不同的Request
types的构造函数,所以它不需要通道版本( 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)