haskell io monads

E/S premonĂ¡lica Haskell



io monads (2)

Me pregunto cómo se hicieron las operaciones de E / S en Haskell en los días en que todavía no se inventó la mónada IO. Cualquiera conoce un ejemplo.

Editar: ¿Puedo hacer I / O sin la IO Monad en el moderno Haskell? Prefiero un ejemplo que funcione con GHC moderno.


@ sepp2k ya aclaró cómo funciona esto, pero quería agregar algunas palabras, akhem, algunas líneas de código.

Realmente lo siento por esto. No creo que sea posible escribir esta función sin inseguroPerformIO

¡Por supuesto que puede! Casi nunca deberías usar insafePerformIO http://chrisdone.com/posts/haskellers

Estoy usando un constructor de tipo de Request ligeramente diferente, para que no tome la versión de canal (como stdin / stdout en el código de @sepp2k ). Aquí está mi solución para esto:

(Nota: getFirstReq no funciona en la lista vacía, tendrías que agregar un caso para eso, pero debería ser trivial)

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)


Antes de que se introdujera la mónada IO, main era una función del tipo [Response] -> [Request] . Una Request representaría una acción de E / S, como escribir en un canal o un archivo, o leer datos de entrada, o leer variables de entorno, etc. Una Response sería el resultado de dicha acción. Por ejemplo, si realizó una solicitud de ReadChan o ReadFile , la Response correspondiente sería Str str donde str sería una String contiene la entrada de lectura. Al realizar una AppendChan , AppendFile o WriteFile , la respuesta sería simplemente Success . (Suponiendo, en todos los casos, que la acción dada fue realmente exitosa, por supuesto).

De modo que un programa Haskell funcionaría al crear una lista de valores de Request y leer las respuestas correspondientes de la lista que se da a main . Por ejemplo, un programa para leer un número del usuario puede verse así (dejando de lado el manejo de errores por simplicidad):

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

Como Stephen Tetley ya señaló en un comentario, en el capítulo 7 del haskell.cs.yale.edu/wp-content/uploads/2011/01/… se da una especificación detallada de este modelo.

¿Puedo hacer I / O sin la IO Monad en el moderno Haskell?

No. Haskell ya no es compatible con la forma de Response / Request de hacer IO directamente y el tipo de main ahora es IO () , por lo que no puedes escribir un programa Haskell que no implique IO e incluso si pudieras, hubieras todavía no tiene forma alternativa de hacer ninguna E / S.

Sin embargo, lo que puede hacer es escribir una función que adopte una función principal antigua y la convierta en una acción IO. Luego puede escribir todo usando el estilo anterior y luego solo usar IO en main donde simplemente invocaría la función de conversión en su función principal real. Hacerlo sería casi más complicado que usar la mónada IO (y confundiría a cualquier Haskeller moderno leyendo tu código), así que definitivamente no lo recomendaría. Sin embargo, es posible. Tal función de conversión podría verse así:

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

A continuación, puede utilizar esta función de esta manera:

-- 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