I'm trying to understand how an IORef (MVar, TVar) might be shared between separate instances of function closures. I've defined a function `count` that returns a function with an IORef "inside",
count :: IORef Int -> Int -> IO (Char -> IO Int) count io i = do writeIORef io i return (\c -> if c == 'a' then modifyIORef io (+1) >> readIORef io else readIORef io)
Now I define an IORef and a couple of counters that share the IORef,
iio :: IO (IORef Int) iio = newIORef 0 ic1 = do { io <- iio ; count io 0 } ic2 = do { io <- iio ; count io 0 }
I expected to see the counters sharing the IORef, so that executing `counter1` below would print "1,1,2,3". Instead, it prints "1,0,1,2".
counter1 = do c1 <- ic1 c2 <- ic2 c1 'a' >>= print c2 'b' >>= print c2 'a' >>= print c1 'a' >>= print
However, if I create the two counters inside the same do block, I get the result I expected, "1,1,2,3".
counter2 = do io <- iio c1 <- count io 0 c2 <- count io 0 c1 'a' >>= print c2 'b' >>= print c2 'a' >>= print c1 'a' >>= print
So apparently my mental picture of an IORef as a pointer to a value is wrong. I need a new mental picture. What's going on here? Thanks, -Rod
Rodney D Price wrote:
I'm trying to understand how an IORef (MVar, TVar) might be shared between separate instances of function closures. I've defined a function `count` that returns a function with an IORef "inside",
count :: IORef Int -> Int -> IO (Char -> IO Int) count io i = do writeIORef io i return (\c -> if c == 'a' then modifyIORef io (+1) >> readIORef io else readIORef io)
Now I define an IORef and a couple of counters that share the IORef,
iio :: IO (IORef Int) iio = newIORef 0 ic1 = do { io <- iio ; count io 0 } ic2 = do { io <- iio ; count io 0 }
I expected to see the counters sharing the IORef, so that executing `counter1` below would print "1,1,2,3". Instead, it prints "1,0,1,2".
counter1 = do c1 <- ic1 c2 <- ic2 c1 'a' >>= print c2 'b' >>= print c2 'a' >>= print c1 'a' >>= print
However, if I create the two counters inside the same do block, I get the result I expected, "1,1,2,3".
counter2 = do io <- iio c1 <- count io 0 c2 <- count io 0 c1 'a' >>= print c2 'b' >>= print c2 'a' >>= print c1 'a' >>= print
So apparently my mental picture of an IORef as a pointer to a value is wrong. I need a new mental picture. What's going on here?
Naming the creation of a new IORef "iio" is not gonna make it return the same IORef every time you call iio. Imagine iio being expanded to its definition in your counter1 example (which is exactly what happens); would you still expect the result to be 1,1,2,3? If you want the IORef to be shared by different pieces of code, pass it around as an argument. Kind regards, Martijn.
My old, deeply flawed mental picture had "iio" taking the role of a pointer to a value. My bright, shiny new mental picture has "iio" acting just like a C #define macro: every time I call "iio", I'm really just writing "newIORef 0". Is that what you're saying? -Rod On Oct 27, 2008, at 4:36 PM, Martijn van Steenbergen wrote:
Rodney D Price wrote:
So apparently my mental picture of an IORef as a pointer to a value is wrong. I need a new mental picture. What's going on here?
Naming the creation of a new IORef "iio" is not gonna make it return the same IORef every time you call iio. Imagine iio being expanded to its definition in your counter1 example (which is exactly what happens); would you still expect the result to be 1,1,2,3? If you want the IORef to be shared by different pieces of code, pass it around as an argument.
Kind regards,
Martijn.
On Mon, 2008-10-27 at 17:02 -0600, Rodney D Price wrote:
My old, deeply flawed mental picture had "iio" taking the role of a pointer to a value.
Not so much flawed: you just need to realize that Haskell considers the sub-program `create a new IORef with contents 0 and return it' to be a perfectly good value, and when you say iio = newIORef 0 Haskell is perfectly happy to point iio at that sub-program. Your problem is distinguishing `program' from `value' and thinking that `value' means `result of program'. Haskell knows no such distinction.
My bright, shiny new mental picture has "iio" acting just like a C #define macro:
But this is a good intuition, too. Except without the weird syntax bugs. Also statically typed. And you can use recursion, etc. Other than that, `Haskell function = macro' isn't a bad (component of a) mental picture.
every time I call "iio", I'm really just writing "newIORef 0".
Write. So you say name = expression in Haskell when `name' is clearer in the contexts where it's used than `expression' is (or when expression needs to be recursive). jcc
On 2008 Oct 27, at 19:02, Rodney D Price wrote:
My old, deeply flawed mental picture had "iio" taking the role of a pointer to a value. My bright, shiny new mental picture has "iio" acting just like a C #define macro: every time I call "iio", I'm really just writing "newIORef 0". Is that what you're saying?
Sort of. What's really happening is that "newIORef" is an I/O action, just like "putStrLn", so will be executed every time it's used just as "putStrLn" is. If you want to do it only once, you must do it only once and pass the resulting IORef around. (There is also a way to do "global variables", but it's rather unsafe and requires telling the compiler to not rewrite the initialization code.) -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH
On Tue, 28 Oct 2008 12:02:54 Rodney D Price wrote:
My old, deeply flawed mental picture had "iio" taking the role of a pointer to a value. My bright, shiny new mental picture has "iio" acting just like a C #define macro: every time I call "iio", I'm really just writing "newIORef 0". Is that what you're saying?
-Rod
No, this isn't the behaviour of IORefs at all - you're getting mixed up with Haskell's syntax. <- in a do block means perform the contained action and let me use the result. = defines a term and is effectively just an alias - it doesn't run anything by itself.
iio :: IO (IORef Int) This means "iio is an IO operation which produces an IORef to an Int" iio = newIORef 0 This means "iio is creating a new counter starting at 0" ic1 = do { io <- iio ; count io 0 } This is an IO operation which runs iio, creating a new IORef in the process, and then starts a counter at 0 and returns it. ic2 = do { io <- iio ; count io 0 } This runs iio again, creating another IORef, and then starts a counter on the new IORef.
Haskell doesn't have mutable global variables - it goes against the grain of a pure language. You have to create the IORef within an IO procedure and pass it in. You really should write: counter = do io <- newIORef 0 c1 <- count io 0 c2 <- count io 0 c1 'a' >>= print c2 'b' >>= print c2 'a' >>= print c1 'a' >>= print This should behave as you expected - it creates the IORef then creates two counters sharing it. Remember that when you return IO a, you're returning an IO operation that produces an a. That operation is not run until it is bound to the main IO monad. Haskell's IO looks like any other language if you're only writing and calling procedures normally but as soon as you start passing around references to IO procedures you need to understand a little more about how monads work. In the real world using IO counters is probably something to avoid. Only the part of your program that is actually interacting with the outside world should use IO at all, and keeping an internal count doesn't need this. Minimise IO and Haskell will reward you. Let IO spread all through your program and it will be no safer than the C the developer was really thinking in. Cheers, Tim
Thanks for all the replies. Perhaps my mental picture is a little less flawed, now, but this brings up something about the IO monad that has always bothered me. Papers on the IO monad say things like "A term of type IO () denotes an action, but does not necessarily perform the action." (Wadler, "How to Declare an Imperative") Or, "putc '!' denotes the command that, if it is ever performed, will print an exclamation mark." Okay... However, when I use IO in a Haskell program, the response is usually pretty snappy. It's not as if the Haskell runtime is hanging around, waiting for some time in the future when it might be appropriate to do IO. It happens right now. Yet the literature gives the impression that the IO monad in particular is a clever trick to preserve referential transparency by gathering up all the IO actions, but not necessarily actually *performing* them. Then the Haskell runtime holds its nose and *performs* them when necessary. But at least a couple responses to my question have said that the IO action is performed when `<-` (or equivalently, bind, >>=) is executed. My head has a hard time holding a mental model of code in which IO might happen at some unspecified time in the future. A mental model in which IO happens when >>= is executed is a lot better fit for my head. Yet, I remain a bit nervous about this new (to me) mental model. Is this just an intuition that works 99.9% of the time, or is it actual, literal fact? -Rod On Oct 27, 2008, at 5:43 PM, Timothy Goddard wrote:
On Tue, 28 Oct 2008 12:02:54 Rodney D Price wrote:
My old, deeply flawed mental picture had "iio" taking the role of a pointer to a value. My bright, shiny new mental picture has "iio" acting just like a C #define macro: every time I call "iio", I'm really just writing "newIORef 0". Is that what you're saying?
-Rod
No, this isn't the behaviour of IORefs at all - you're getting mixed up with Haskell's syntax. <- in a do block means perform the contained action and let me use the result. = defines a term and is effectively just an alias - it doesn't run anything by itself.
iio :: IO (IORef Int) This means "iio is an IO operation which produces an IORef to an Int" iio = newIORef 0 This means "iio is creating a new counter starting at 0" ic1 = do { io <- iio ; count io 0 } This is an IO operation which runs iio, creating a new IORef in the process, and then starts a counter at 0 and returns it. ic2 = do { io <- iio ; count io 0 } This runs iio again, creating another IORef, and then starts a counter on the new IORef.
Haskell doesn't have mutable global variables - it goes against the grain of a pure language. You have to create the IORef within an IO procedure and pass it in. You really should write:
counter = do io <- newIORef 0 c1 <- count io 0 c2 <- count io 0 c1 'a' >>= print c2 'b' >>= print c2 'a' >>= print c1 'a' >>= print
This should behave as you expected - it creates the IORef then creates two counters sharing it.
Remember that when you return IO a, you're returning an IO operation that produces an a. That operation is not run until it is bound to the main IO monad. Haskell's IO looks like any other language if you're only writing and calling procedures normally but as soon as you start passing around references to IO procedures you need to understand a little more about how monads work.
In the real world using IO counters is probably something to avoid. Only the part of your program that is actually interacting with the outside world should use IO at all, and keeping an internal count doesn't need this. Minimise IO and Haskell will reward you. Let IO spread all through your program and it will be no safer than the C the developer was really thinking in.
Cheers,
Tim _______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
On 2008 Oct 27, at 20:25, Rodney D Price wrote:
Okay... However, when I use IO in a Haskell program, the response is usually pretty snappy. It's not as if the Haskell runtime is hanging around, waiting for some time in the future when it might be appropriate to do IO. It happens right now. Yet the literature gives the impression that the IO monad in particular is a clever trick to preserve referential transparency by gathering up all the IO actions, but not necessarily actually *performing* them. Then the Haskell runtime holds its nose and *performs* them when necessary.0
The *conceptual* model for the IO monad is that "main" returns a big IO action which is then evaluated by the Haskell runtime. As a practical matter, this usually behaves the same as if <- actually did evaluation. (It doesn't. It binds monadic expressions together, and is a convenient way to use the (>>=) operator.) The one difference is its interaction with Haskell equations (a = b); since those are more or less macro definitions, assigning e.g. an expression of type IO String to such a "macro" will cause the expression to be substituted wherever the "macro" is used. IO is a very atypical monad, by the way. Someone pointed you earlier to the "IO Inside" page, which describes the internal tricks that make IO work. I prefer to think of IO actions as partially applied functions, with the missing argument being a "RealWorld" that is hidden inside the IO monad. (think: IO a = State RealWorld a. This isn't quite correct because the state also has IORefs inside it.) -- brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu electrical and computer engineering, carnegie mellon university KF8NH
This would be an excellent thread for the Haskell Cafe mailing list, haskell-cafe@haskell.org However, it's becoming over-long for the main list http://haskell.org/haskellwiki/Mailing_lists#Mailing_lists_in_detail Thanks! Simon | -----Original Message----- | From: haskell-bounces@haskell.org [mailto:haskell-bounces@haskell.org] On | Behalf Of Brandon S. Allbery KF8NH | Sent: 28 October 2008 00:38 | To: Rodney D Price | Cc: haskell@haskell.org | Subject: Re: [Haskell] IORef sharing | | On 2008 Oct 27, at 20:25, Rodney D Price wrote: | > Okay... However, when I use IO in a Haskell program, | > the response is usually pretty snappy. It's not as | > if the Haskell runtime is hanging around, waiting for | > some time in the future when it might be appropriate | > to do IO. It happens right now. Yet the literature | > gives the impression that the IO monad in particular | > is a clever trick to preserve referential transparency | > by gathering up all the IO actions, but not necessarily | > actually *performing* them. Then the Haskell runtime | > holds its nose and *performs* them when necessary.0 | | The *conceptual* model for the IO monad is that "main" returns a big | IO action which is then evaluated by the Haskell runtime. | | As a practical matter, this usually behaves the same as if <- actually | did evaluation. (It doesn't. It binds monadic expressions together, | and is a convenient way to use the (>>=) operator.) The one | difference is its interaction with Haskell equations (a = b); since | those are more or less macro definitions, assigning e.g. an expression | of type IO String to such a "macro" will cause the expression to be | substituted wherever the "macro" is used. | | IO is a very atypical monad, by the way. Someone pointed you earlier | to the "IO Inside" page, which describes the internal tricks that make | IO work. I prefer to think of IO actions as partially applied | functions, with the missing argument being a "RealWorld" that is | hidden inside the IO monad. (think: IO a = State RealWorld a. This | isn't quite correct because the state also has IORefs inside it.) | | -- | brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com | system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu | electrical and computer engineering, carnegie mellon university KF8NH | | | _______________________________________________ | Haskell mailing list | Haskell@haskell.org | http://www.haskell.org/mailman/listinfo/haskell
Rodney D Price wrote:
Okay... However, when I use IO in a Haskell program, the response is usually pretty snappy. It's not as if the Haskell runtime is hanging around, waiting for some time in the future when it might be appropriate to do IO. It happens right now. Yet the literature gives the impression that the IO monad in particular is a clever trick to preserve referential transparency by gathering up all the IO actions, but not necessarily actually *performing* them. Then the Haskell runtime holds its nose and *performs* them when necessary.
It's really best to think of 'main' as returning an IO action. You can call >>= all over the place, but if you never return the result up through 'main', the resulting bound action will never be executed. (Even then it might not be executed, if the runtime never passes through that point in your program.) Coming from an imperative background, I imagine that 'main' returns pretty much immediately, and the runtime starts executing your action right away. To put it another way, 'main' is a constant -- it doesn't do work, so much as it describes what you want the runtime to do. Hope this helps!
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Oct 27, 2008, at 7:25 PM, Rodney D Price wrote:
Perhaps my mental picture is a little less flawed, now, but this brings up something about the IO monad that has always bothered me. Papers on the IO monad say things like "A term of type IO () denotes an action, but does not necessarily perform the action." (Wadler, "How to Declare an Imperative") Or, "putc '!' denotes the command that, if it is ever performed, will print an exclamation mark."
I like to think of the IO monad as a lazy imperative program generator. That is, the runtime lazily evaluates actions as they are needed, perhaps as though main is a (potentially infinite) lazy list of actions. In my mental model, runtime basically works like: What should I do first? *evaluate first action* *perform action* What should I do next? *evaluate next action* *perform action* What should I do next? *evaluate next action* *perform action* What should I do next? *evaluate next action* *perform action* ... - - Jake -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.8 (Darwin) iEYEARECAAYFAkkGiCoACgkQye5hVyvIUKkBhACgsT4zg5D+FBkLx7+qBu4xcWvF gYwAnR+wrDdipCHAhJP2cTNcoq54ZklU =OM1+ -----END PGP SIGNATURE-----
iio :: IO (IORef Int) iio = newIORef 0
I sometimes feel like "IO" should be renamed to "CommandSequenceReturning". So the above would read: iio :: CommandSequenceReturning (IORef Int) iio = newIORef 0 I.e. iio is not an IORef Int but only a (trivial) sequence of commands that will end up returning an object of (IORef Int) when it'll be executed. Stefan
participants (10)
-
Brandon S. Allbery KF8NH -
Eli Ford -
Jake Mcarthur -
Jonathan Cast -
Martijn van Steenbergen -
Rodney D Price -
Rodney D Price -
Simon Peyton-Jones -
Stefan Monnier -
Timothy Goddard