background question about IO monad

Hello, haskellers, I have a question for you about the IO monad. On one level, I seem to be "getting" it, at least I seem to be writing code that does what I want, but on another level I am almost certainly not at all clear on some concepts. In various tutorials around the web, I keep finding this notion that the IO monad is "one-way", that you can put stuff into it, but you can't take it out. (And, damn, some of the contortions I went through while trying to figure out how to do exceptions in my little scheme interpreter certainly bear that out! I was sure beating my head against liftIO et al for a fair while...) But let me post some code snippets here:
lispUTCTime [] = doIOAction (getClockTime) toS allErrs where toS val = String (calendarTimeToString (toUTCTime val))
lispUTCTime [IntNumber n] = return (String (calendarTimeToString (toUTCTime (TOD n 0))))
This is a little function I added to the interpreter a couple of days ago: if you enter (UTCtime) with no arguments, it gets the current time and formats it as UTC: like so; this came from the first alternative above: lisp> (UTCtime) "Wed Feb 6 03:57:45 UTC 2008" and if you give an argument, you get that interpreted as a number of seconds since epoch, and that gets formatted as UTC; this is from the second alternative above: lisp> (UTCtime 1.203e9) "Thu Feb 14 14:40:00 UTC 2008" And here's the doIOAction routine: I wrote this, it's not some system-level routine.
doIOAction action ctor epred = do ret <- liftIO (try action) case ret of Left err -> if epred err then throwError (Default (show err)) else return (Bool False) Right val -> return (ctor val)
OK, with all that as background, on one level I understand why I need the doIOAction routine in the first version of lispUTCTime: I'm calling getClockTime, that's an IO action, so I enter the IO monad, get the time, and return: all is cool. In the second version, all I'm doing is taking a number and interpreting it as a time, and writing that in a particular format; again, no problem. But after that, it sure seems to me as if I've taken data out of the IO monad... haven't I? Given that the second alternative never entered doIOAction and that after both are done I have a string of characters, prettily formatted to indicate a time, that's what it feels like to this unwashed C programmer. So, what's going on there? What's the difference between the two alternatives? I would appreciate any enlightenment you can send my way! regards, Uwe

On Feb 5, 2008 9:44 PM, Uwe Hollerbach
lisp> (UTCtime) "Wed Feb 6 03:57:45 UTC 2008" --- lisp> (UTCtime 1.203e9) "Thu Feb 14 14:40:00 UTC 2008" -- But after that, it sure seems to me as if I've taken data out of the IO monad... haven't I? Given that the second alternative never entered doIOAction and that after both are done I have a string of characters, prettily formatted to indicate a time, that's what it feels like to this unwashed C programmer.
Formatting a time is a completely pure operation. If you give the time formatting function the same timestamp, you always get the same string back. It is getting the *current* time which is in the IO monad, since it "returns" different results depending on at what time it is called. Luke

Hello Uwe, Wednesday, February 6, 2008, 7:44:27 AM, you wrote:
But after that, it sure seems to me as if I've taken data out of the IO monad...
this means that you can't use results of IO actions in pure functions. your code works in some transformed version of IO monad, so you don't escaped it if we call pure functions as "functions" and non-pure ones as "procedures", the rule is "functions can't call procedures", but all other activity is possible. in your do_action you calls procedure (to format current time) and call a function (to format given time). do_action is procedure (because it works in transformed IO monad), so you don't break any rules -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

Hi, all, thanks for the responses. I understand the distinction
between pure functions and impure functions/procedures/IO actions, it
just felt to me in the samples that I quoted that I was in fact
starting from basically the starting point, eventually getting to the
same endpoint (or at least a pair of endpoints that are not easily
distinguished from each other by looking just at code), and inbetween
one path was going through liftIO and the other not. But I guess it
comes down to the fact that, since I'm in a REPL, I'm wallowing in
impurity all the time (or something like that :-) )
regards,
Uwe
On 2/6/08, Bulat Ziganshin
Hello Uwe,
Wednesday, February 6, 2008, 7:44:27 AM, you wrote:
But after that, it sure seems to me as if I've taken data out of the IO monad...
this means that you can't use results of IO actions in pure functions. your code works in some transformed version of IO monad, so you don't escaped it
if we call pure functions as "functions" and non-pure ones as "procedures", the rule is "functions can't call procedures", but all other activity is possible. in your do_action you calls procedure (to format current time) and call a function (to format given time). do_action is procedure (because it works in transformed IO monad), so you don't break any rules
-- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

On 6 Feb 2008, at 7:30 PM, Uwe Hollerbach wrote:
Hi, all, thanks for the responses. I understand the distinction between pure functions and impure functions/procedures/IO actions,
Um, I'm not sure of that, given what you go on to say.
it just felt to me in the samples that I quoted that I was in fact starting from basically the starting point,
Not really. The key difference to understand is that between difference between getCurrentTime and toUTCTime 42. These are your `starting points' --- the rest is just pure code.
eventually getting to the same endpoint (or at least a pair of endpoints that are not easily distinguished from each other by looking just at code), and inbetween
Well, the in-between paths are pure (or can be) either way. It's the starting point that needs liftIO or not.
one path was going through liftIO and the other not. But I guess it comes down to the fact that, since I'm in a REPL, I'm wallowing in impurity all the time (or something like that :-) )
jcc

Well, you may well be right! Just think of me as a planarian at the opera... :-)
On 2/6/08, Jonathan Cast
On 6 Feb 2008, at 7:30 PM, Uwe Hollerbach wrote:
Hi, all, thanks for the responses. I understand the distinction between pure functions and impure functions/procedures/IO actions,
Um, I'm not sure of that, given what you go on to say.
it just felt to me in the samples that I quoted that I was in fact starting from basically the starting point,
Not really. The key difference to understand is that between difference between getCurrentTime and toUTCTime 42. These are your `starting points' --- the rest is just pure code.
eventually getting to the same endpoint (or at least a pair of endpoints that are not easily distinguished from each other by looking just at code), and inbetween
Well, the in-between paths are pure (or can be) either way. It's the starting point that needs liftIO or not.
one path was going through liftIO and the other not. But I guess it comes down to the fact that, since I'm in a REPL, I'm wallowing in impurity all the time (or something like that :-) )
jcc

All right, after a bit of dinner and some time to mess about, here's another attempt to check my understanding: here is a simplified version of the lisp-time example:
module Main where import System.Time
pure_fn :: Integer -> String pure_fn n = calendarTimeToString (toUTCTime (TOD n 0))
wicked_fn :: IO String wicked_fn = getClockTime >>= return . pure_fn . toI where toI (TOD n _) = n
make_wicked :: String -> IO String make_wicked str = return str
-- use of pure_fn -- main = putStrLn (pure_fn 1230000000)
-- use of wicked_fn -- main = wicked_fn >>= putStrLn
-- use of make_wicked main = (make_wicked (pure_fn 1234567890)) >>= putStrLn
If I use the first of the three "main" alternatives, I'm calling a pure function directly: it takes an integer, 123..., and produces a string. If I pass the same integer to the pure function, I'll get the same value, every time. This string is passed to putStrLn, an IO action, in order that I may gaze upon it, but the string itself is not thereby stuck in the IO monad. If I use the second of the three "main" alternatives, I'm calling an IO action: wicked_fn, which returns the current time formatted as UTC. In principle, every time I call wicked_fn, I could get a different answer. Because it's an IO action, I can't just pass it to putStrLn in the same way I passed in the previous pure_fn value, but instead I have to use the bind operator >>=. If I use the third of the "main" alternatives, I am starting with a pure function: it's that number formatted as UTC (it happens to come to Fri Feb 13 of next year), but then I pass it through the make_wicked function, which transmogrifies it into the IO monad. Therefore, as in the above, I have to use >>= in order to get it to work; "putStrLn (make_wicked (pure_fn 123...))" doesn't work. <deep breath> OK, after all that, my original question, in terms of this example: "the IO monad is one-way" is equivalent to saying there is no haskell function that I could write that would take
(make_wicked (pure_fn 123456))
and make it into something that could be used in the same way and the same places as just plain
(pure_fn 123456)
? And, coming back to my scheme interpreter, this is at least somewhat irrelevant, because, since I am in a REPL of my own devising, I'm firmly in IO-monad-land, now and forever. Right? thanks, Uwe

On Feb 7, 2008 7:32 AM, Uwe Hollerbach
pure_fn :: Integer -> String pure_fn n = calendarTimeToString (toUTCTime (TOD n 0))
make_wicked :: String -> IO String make_wicked str = return str
-- use of make_wicked main = (make_wicked (pure_fn 1234567890)) >>= putStrLn
OK, after all that, my original question, in terms of this example: "the IO monad is one-way" is equivalent to saying there is no haskell function that I could write that would take
(make_wicked (pure_fn 123456))
and make it into something that could be used in the same way and the same places as just plain
(pure_fn 123456)
?
Spot on! Luke

On 6 Feb 2008, at 11:32 PM, Uwe Hollerbach wrote:
All right, after a bit of dinner and some time to mess about, here's another attempt to check my understanding: here is a simplified version of the lisp-time example:
module Main where import System.Time
pure_fn :: Integer -> String pure_fn n = calendarTimeToString (toUTCTime (TOD n 0))
wicked_fn :: IO String wicked_fn = getClockTime >>= return . pure_fn . toI where toI (TOD n _) = n
make_wicked :: String -> IO String make_wicked str = return str
-- use of pure_fn -- main = putStrLn (pure_fn 1230000000)
-- use of wicked_fn -- main = wicked_fn >>= putStrLn
-- use of make_wicked main = (make_wicked (pure_fn 1234567890)) >>= putStrLn
If I use the first of the three "main" alternatives, I'm calling a pure function directly: it takes an integer, 123..., and produces a string. If I pass the same integer to the pure function, I'll get the same value, every time. This string is passed to putStrLn, an IO action, in order that I may gaze upon it, but the string itself is not thereby stuck in the IO monad.
If I use the second of the three "main" alternatives, I'm calling an IO action: wicked_fn, which returns the current time formatted as UTC. In principle, every time I call wicked_fn, I could get a different answer. Because it's an IO action, I can't just pass it to putStrLn in the same way I passed in the previous pure_fn value, but instead I have to use the bind operator >>=.
If I use the third of the "main" alternatives, I am starting with a pure function: it's that number formatted as UTC (it happens to come to Fri Feb 13 of next year), but then I pass it through the make_wicked function, which transmogrifies it into the IO monad. Therefore, as in the above, I have to use >>= in order to get it to work; "putStrLn (make_wicked (pure_fn 123...))" doesn't work.
<deep breath>
OK, after all that, my original question, in terms of this example: "the IO monad is one-way" is equivalent to saying there is no haskell function that I could write that would take
(make_wicked (pure_fn 123456))
and make it into something that could be used in the same way and the same places as just plain
(pure_fn 123456)
?
And, coming back to my scheme interpreter, this is at least somewhat irrelevant, because, since I am in a REPL of my own devising, I'm firmly in IO-monad-land, now and forever.
Right. jcc

On 2/6/08, Uwe Hollerbach
And, coming back to my scheme interpreter, this is at least somewhat irrelevant, because, since I am in a REPL of my own devising, I'm firmly in IO-monad-land, now and forever.
This is not entirely true; a REPL can be pure. Consider the following simple stack-based-calculator; all the IO happens within "interact", the REPL itself is pure: import System.IO main = hSetBuffering stdout NoBuffering >> interact replMain replMain s = "Stack calculator\n> " ++ repl [] s repl :: [Int] -> String -> String repl _ [] = "" repl _ ('q':_) = "" repl s ('\n':xs) = show s ++ "\n> " ++ repl s xs repl s xs@(x:_) | x >= '0' && x <= '9' = let (v, xs') = head $ reads xs in repl (v:s) xs' repl s (c:xs) | c `elem` validCommands = case command c s of Just s' -> repl s' xs Nothing -> "<stack underflow>\n" ++ repl s xs repl s (_:xs) = repl s xs -- ignore unrecognized characters validCommands = ".d+c" command :: Char -> [Int] -> Maybe [Int] command '.' (x:xs) = Just xs command 'd' (x:xs) = Just $ x:x:xs command '+' (x:y:xs) = Just $ (x+y):xs command 'c' _ = Just [] command _ _ = Nothing You can go further than "interact" if you want to abstract away the impurity in your system and take input from some outside process which has a limited set of impure operations. Take a look here for an example using "Prompt" (which has seen some discussion here on haskell-cafe): http://paste.lisp.org/display/53766 In that example, "guess n" is an action in the pure Prompt monad; different interpretation functions allow this monad to interact with an AI (in a semi-pure setting; it outputs strings), or with a real player via the full IO interface. A similar mechanism could be used for the scheme REPL to make it as "pure" as possible, with "getClockTime" being replaced by "prompt GetClockTime" to interact with the outside world.

Thanks, I'm going to have to study this a bit...
Uwe
On 2/7/08, Ryan Ingram
On 2/6/08, Uwe Hollerbach
wrote: And, coming back to my scheme interpreter, this is at least somewhat irrelevant, because, since I am in a REPL of my own devising, I'm firmly in IO-monad-land, now and forever.
This is not entirely true; a REPL can be pure.
Consider the following simple stack-based-calculator; all the IO happens within "interact", the REPL itself is pure:
import System.IO
main = hSetBuffering stdout NoBuffering >> interact replMain
replMain s = "Stack calculator\n> " ++ repl [] s
repl :: [Int] -> String -> String repl _ [] = "" repl _ ('q':_) = "" repl s ('\n':xs) = show s ++ "\n> " ++ repl s xs repl s xs@(x:_) | x >= '0' && x <= '9' = let (v, xs') = head $ reads xs in repl (v:s) xs' repl s (c:xs) | c `elem` validCommands = case command c s of Just s' -> repl s' xs Nothing -> "<stack underflow>\n" ++ repl s xs repl s (_:xs) = repl s xs -- ignore unrecognized characters
validCommands = ".d+c" command :: Char -> [Int] -> Maybe [Int] command '.' (x:xs) = Just xs command 'd' (x:xs) = Just $ x:x:xs command '+' (x:y:xs) = Just $ (x+y):xs command 'c' _ = Just [] command _ _ = Nothing
You can go further than "interact" if you want to abstract away the impurity in your system and take input from some outside process which has a limited set of impure operations. Take a look here for an example using "Prompt" (which has seen some discussion here on haskell-cafe): http://paste.lisp.org/display/53766
In that example, "guess n" is an action in the pure Prompt monad; different interpretation functions allow this monad to interact with an AI (in a semi-pure setting; it outputs strings), or with a real player via the full IO interface. A similar mechanism could be used for the scheme REPL to make it as "pure" as possible, with "getClockTime" being replaced by "prompt GetClockTime" to interact with the outside world.

Uwe Hollerbach wrote:
lispUTCTime [] = doIOAction (getClockTime) toS allErrs where toS val = String (calendarTimeToString (toUTCTime val))
here you use liftIO (hidden in doIOAction) to use an IO action (getClockTime) inside of a different monad wich contains IO at it's base. so your custom SchemeInterpreterMonad (however it's called) is basically just an extended IO monad.
lispUTCTime [IntNumber n] = return (String (calendarTimeToString (toUTCTime (TOD n 0))))
here you use a pure computation to produce the result string, but then you use return to put it in the monad, too. since IO lives at the base of your custom monad, return will put the String into IO, too. you can't take the IO String out of the IO monad, but you can put the pure String in the IO monad using return.
But after that, it sure seems to me as if I've taken data out of the IO monad... haven't I? Given that the second alternative never entered doIOAction and that after both are done I have a string of characters, prettily formatted to indicate a time, that's what it feels like to this unwashed C programmer.
As explained above, the second alternative uses return instead of doIOAction to "enter" the monad. From a Haskell programmers point of view, you don't have a pure string of characters, but you have a monadic action producing such a string in both cases. That is fine, since your program is inside a REPL anyway, and the very next thing you want to do is to print the computed value, wich is another IO action. but from the point of view of the Scheme programmer using your interpreter, there is no special IO monad around, since the whole Scheme interpreter lives in IO and IO is available to him everywhere. Tillmann
participants (6)
-
Bulat Ziganshin
-
Jonathan Cast
-
Luke Palmer
-
Ryan Ingram
-
Tillmann Rendel
-
Uwe Hollerbach