我想知道在IO monad尚未发明的时代,Haskell中的I / O是如何完成的。任何人都知道一个例子。
编辑:现代Haskell中没有IO Monad可以完成I / O吗?我更喜欢一个适用于现代GHC的例子。
答案 0 :(得分:58)
在引入IO monad之前,main
是类型[Response] -> [Request]
的函数。 Request
表示I / O操作,例如写入通道或文件,读取输入或读取环境变量等。Response
将是此类操作的结果。例如,如果您执行了ReadChan
或ReadFile
请求,则相应的Response
将是Str str
,其中str
将是String
包含已读取的内容输入。执行AppendChan
,AppendFile
或WriteFile
请求时,响应只会是Success
。 (在所有情况下,假设给定的行动实际上是成功的)。
因此,Haskell程序可以通过构建Request
值列表并从给定main
的列表中读取相应的响应来工作。例如,从用户读取数字的程序可能看起来像这样(为简单起见省略任何错误处理):
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 Report的第7章给出了该模型的详细说明。
现代Haskell中没有IO Monad可以完成I / O吗?
没有。 Haskell不再支持直接执行IO的Response
/ Request
方式,而main
的类型现在是IO ()
,所以你不能编写一个没有的Haskell程序。涉及IO
,即使你可以,你仍然没有别的办法做任何I / O.
但是,您可以执行的操作是编写一个函数,该函数采用旧式main函数并将其转换为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
答案 1 :(得分:1)
我更喜欢一个与现代GHC兼容的示例。
对于GHC 8.6.5:
import Control.Concurrent.Chan(newChan, getChanContents, writeChan)
import Control.Monad((<=<))
type Dialogue = [Response] -> [Request]
data Request = Getq | Putq Char
data Response = Getp Char | Putp
runDialogue :: Dialogue -> IO ()
runDialogue d =
do ch <- newChan
l <- getChanContents ch
mapM_ (writeChan ch <=< respond) (d l)
respond :: Request -> IO Response
respond Getq = fmap Getp getChar
respond (Putq c) = putChar c >> return Putp
其中类型声明来自Philip Wadler的How to Declare an Imperative第14页。测试程序留给好奇的读者练习:-)
如果有人想知道:
-- from ghc-8.6.5/libraries/base/Control/Concurrent/Chan.hs, lines 132-139
getChanContents :: Chan a -> IO [a]
getChanContents ch
= unsafeInterleaveIO (do
x <- readChan ch
xs <- getChanContents ch
return (x:xs)
)
是-unsafeInterleaveIO
确实出现了。
答案 2 :(得分:0)
@ sepp2k 已经澄清了这是如何运作的,但我想补充几句话
我真的很抱歉。我认为没有unsafePerformIO
就可以编写这个函数
当然可以,你几乎不应该使用unsafePerformIO http://chrisdone.com/posts/haskellers
我使用稍微不同的Request
类型构造函数,因此它不会像 @ sepp2k的代码那样使用频道版本stdin
/ stdout
。以下是我的解决方案:
(注意: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)