在引入 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 不再支持 Response/Request 直接做 IO 的方式,main 的类型现在是 IO (),所以你不能写一个不涉及 IO 的 Haskell 程序,即使你可以,你仍然没有其他方法来做任何 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