Haskell Pre-Monadic I / O

I wonder how I / O was made in Haskell in the days when the IO monad was still not invented. Does anyone know an example.

Edit: Can I / O work without IO Monad in modern Haskell? I would prefer an example that works with modern GHC.

+42
io haskell monads
Jun 08 '13 at 17:39
source share
2 answers

Before the IO monad was introduced, main was a function of type [Response] -> [Request] . A Request will be an I / O operation, such as writing to a channel or file, reading input or reading environment variables, etc. A Response will be the result of such an action. For example, if you ReadChan or ReadFile , the corresponding Response would be Str str , where str would be a String containing the read input. When executing an AppendChan , AppendFile or WriteFile request, the response will simply be Success . (Assuming, in all cases, that the action was truly successful, of course).

So, the Haskell program will work by creating a list of Request values ​​and reading the appropriate responses from the list specified in main . For example, a program for reading a number from a user may look like this (without any simplicity):

 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 

As Stephen Tetley noted in a comment, a detailed specification of this model is given in Chapter 7 1.2 of the Haskell Report .




Can I / O work without IO Monad in modern Haskell?

No. Haskell no longer supports the Response / Request method for entering IO directly, and the main type is now IO () , so you cannot write a Haskell program that does not include IO , and even if you could, you still would not have an alternative way input / output.

However, you can write a function that takes the main function of the old style and turns it into an I / O action. Then you can write everything using the old style, and then use only IO in main , where you just call the conversion function on your real main function. It would almost certainly be more cumbersome than using the IO monad (and confuse the hell out of any modern Haskeller reading your code), so I would definitely not recommend it. However, this is possible. Such a conversion function might look like this:

 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 

Then you can use this function as follows:

 -- 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 
+54
Jun 08 '13 at 22:21
source share

@ sepp2k already figured out how this works, but I wanted to add a few words, akim, a few lines of code.

I'm so sorry about that. I do not think that this function can be written without unsafePerformIO

Of course you can! You should almost never use unsafePerformIO http://chrisdone.com/posts/haskellers

I use a slightly different constructor of type Request , so that it does not accept the channel version ( stdin / stdout , as in @ sepp2k ). Here is my solution for this:

(Note: getFirstReq does not work in an empty list, you will need to add a case to it, bu should be 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) 
0
Jun 06 '17 at 18:32
source share



All Articles