Интересно, как I/O были сделаны в Haskell в дни, когда IO монада все еще не была изобретена. Кто-нибудь знает пример.
Изменить: может ли I/O работать без IO Monad в современном Haskell? Я бы предпочел пример, который работает с современным GHC.
Интересно, как I/O были сделаны в Haskell в дни, когда IO монада все еще не была изобретена. Кто-нибудь знает пример.
Изменить: может ли I/O работать без IO Monad в современном Haskell? Я бы предпочел пример, который работает с современным GHC.
До того, как была введена монада IO, main
была функцией типа [Response] -> [Request]
. A Request
будет представлять собой операцию ввода-вывода, например запись в канал или файл, чтение ввода или чтение переменных среды и т.д. A 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
Как уже отмечал Стивен Тетли в комментарии, подробная спецификация этой модели приведена в главе 7 1.2 Haskell Report.
Можно ли I/O работать без IO Monad в современном Haskell?
Нет. Haskell больше не поддерживает метод Response
/Request
для ввода IO напрямую, а тип main
теперь IO ()
, поэтому вы не можете записать программу Haskell, которая не включает IO
, и даже если вы могли бы, у вас все равно не было бы альтернативного способа ввода/вывода.
Однако вы можете написать функцию, которая принимает основную функцию старого стиля и превращает ее в действие ввода-вывода. Затем вы можете написать все, используя старый стиль, а затем использовать только IO в main
, где вы просто вызываете функцию преобразования на вашей реальной основной функции. Это почти наверняка было бы более громоздким, чем использование монады IO
(и путало бы ад из любого современного 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 уже выяснил, как это работает, но я хотел добавить несколько слов, аким, несколько строк кода.
Мне очень жаль это. Я не думаю, что эту функцию можно написать без unsafePerformIO
Конечно, вы можете! Вы почти никогда не должны использовать unsafePerformIO http://chrisdone.com/posts/haskellers
Я использую немного другой конструктор типа Request
, так что он не принимает версию канала (stdin
/stdout
, как в @sepp2k). Вот мое решение для этого:
(Примечание: getFirstReq
не работает в пустом списке, вам нужно будет добавить к нему случай, bu должно быть тривиально)
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)