Re: IO behaves oddly if used nested
On 20031004T181216+0100, Alastair Reid wrote: be careful to distinguish 'a value of type t' from 'a computation which returns a value of type t'i I always found the idea of a computation as a value a little hard to grasp. Therefore, when I introduced monadic transput in my functional programming course [1], I took the following approach: IO a is the type of a program with an exit value of type a; return, fail, putChar and getChar are primitive programs, and monadic transput is about combining programs using (=). The idea of denoting programs without executing them seems natural (in contrast to denoting computations without executing them). Likewise, it seems natural that there is a special mechanism for actually executing a program: either you give it to GHCi, or you bind it to Main.main and compile the module (thus, the program becomes synonymous with the expression that you bind to Main.main). Up until now, nobody in my class seems to have had problems with this mental model of monadic transput (of course, in grand tradition of Finnish universities, a large subset of my students are not actually attending sessions, so I wouldn't know about them until exam time). [1] Advanced-level introduction to FP; students are advanced undergrads or pre-master graduates. They have a strong background in imperattive programming, with the usual load of data structures and algorithms, operating systems, automata and formal languages theory (and other stuff depending on their tastes). (Note that printing a program is also a natural idea, and likewise natural is the idea that the output is unintelligible.) -- Antti-Juhani Kaijanaho, FM (MSc), http://www.mit.jyu.fi/antkaij/ ohjelmistotekniikan assistentti* assistant in software engineering Jyväskylän yliopisto * University of Jyväskylä Tietotekniikan laitos * Dept. of Mathematical Inf. Tech. signature.asc Description: Digital signature ___ Haskell mailing list [EMAIL PROTECTED] http://www.haskell.org/mailman/listinfo/haskell
Re: IO behaves oddly if used nested
[snip] Neither example is odd behavior, unless you consider Hugs providing a perfectly reasonable instance of Show for IO a odd. True, every program behaves exactly as according to the definition of the run-time behavior of Haskell programs. (Hugs deviates a bit from ghc but ah well) The odd is not in the run-time behavior. The odd is in the conceptual explanation. If I give a description of some f x = y function in Haskell I expect that some program f x is reduced to y and the result is given back (possibly printed). A good story to sell to students. This is almost everywhere the case except for the IO monad. In the former example, the inner putStr is _not_ evaluated but a message is shown (hinting that a IO has some structure we can observe before evaluation). In the latter example, it is shown that we are not interested in a result of a IO program, but only in its (lazily generated) side-effects. The monad behaves oddly with respect to the f x = y behavior. I think I observe the following reactions when I explain IO: 1* Can't the guy in front of me stop poking his nose? (the blank faces and parse errors) 2* Hmm, feels like Java, looks like Java, ahah! is Java! (coders) 3* Hmm, feels like math, looks like math, ahah! is math! (designers and thinkers) Group 1 will mimic, group 2 will produce obfuscuated Java code in Haskell, group 3 has great difficulty with the IO monad (although they are the first group who will write OK haskell programs) because it behaves differently from what they expect. The usual questions of group 3: * Why is an IO a evaluated if I am not interested in it's result? (opposite to the f x = y lazy behavior) * Why is in the putStr hello world example Hello World not shown? (opposite to expected f x = y eval-first-then-show behavior) * Why is in the IO (IO ()) example the inner IO () not evaluated? (somewhat opposite to expected f (f x) behavior - I personally wonder if it is even sound in a category theoretical setting) * ...Lots of other questions... Hmm, have to finish this email now - time constraints. I guess the short story just is: IO monads == sometimes bad conceptual story to sell. Especially so since the best group has the biggest problems and non-trivial examples are only explainable in the end in terms of run-time behavior. What was that remark again about pointy headed people and Haskell? ;-) For that I also gave the Nomad datatype as a possible solution (needs a pretty hefty type checker though). A nice (old) idea would be to represent IO as programs which are interpreted by some _outside_ RTS in a given manner, and leave the Haskell language clean. (It might even be a good idea with respect to the compiler implementation since it removes checking against unsafe IO behavior from the compiler -- just a thought) Bye now, and cheers, l4t3r _ The new MSN 8: smart spam protection and 2 months FREE* http://join.msn.com/?page=features/junkmail ___ Haskell mailing list [EMAIL PROTECTED] http://www.haskell.org/mailman/listinfo/haskell
Re: IO behaves oddly if used nested
The odd is in the conceptual explanation. If I give a description of some f x = y function in Haskell I expect that some program f x is reduced to y and the result is given back (possibly printed). A good story to sell to students. This is true of IO as well. The bit that's tripping up your students is the question of what an object of type 'IO Int' is. It sound like they think it is the same as Int. But this isn't so - no more than an object of type [Int] or 'Maybe Int' is the same as an Int. It sounds like you're trying to make that distinction when you talk about the 'Nomad' datatype but you have to be careful to keep on making that distinction every time you talk about monads and be careful to distinguish 'a value of type t' from 'a computation which returns a value of type t' or, at least, keep making that distinction until after your audience fully appreciates the distinction and can handle more informal explanations. Once they get round the idea that 'IO Int /= Int', surely it is reasonable to accept that '1' and 'return 1' might be printed in different ways? And if you've previously shown them that 'show id' produces a fairly uninformative result, maybe they can accept that 'show (do{c - getChar; putChar c})' produces a similarly uninformative result? You might also try to warm students up to the issues by having them think about what (incorrectly typed) expressions like: ord getChar + ord getChar or (getChar, getChar) or let f x = do{ print x; return (x+1) } in drop 3 [ f i | i - [1..10] ] might do under different evaluation orders. Understanding the problem makes it clearer why we want 'IO Int' /= 'Int'. * Why is an IO a evaluated if I am not interested in it's result? (opposite to the f x = y lazy behavior) You are interested in its result though. If you recall my example of mailing an order for socks to a clothing company, evaluating a value of type 'IO Int' corresponds to opening the envelope containing the order. Opening the envelope does not, in itself, lead to socks being sent. For example, they may decide to discard the first 3 valid orders they receive each day or they may reject orders from people with a 'b' in their name. It is only when they decide to execute the order than the socks get sent. * Why is in the putStr hello world example Hello World not shown? (opposite to expected f x = y eval-first-then-show behavior) For the same reason that my feet get cold and ink-stained if I wear orders for socks instead of wearing socks. The value 'putStr hello world' is a computation which will print hello world when you execute it. Evaluating the computation is not the same as executing it. * Why is in the IO (IO ()) example the inner IO () not evaluated? (somewhat opposite to expected f (f x) behavior - I personally wonder if it is even sound in a category theoretical setting) It _is_ evaluated. But evaluating a value is not the same as executing it just as opening an order is not the same as obeying instructions in the order. A nice (old) idea would be to represent IO as programs which are interpreted by some _outside_ RTS in a given manner, and leave the Haskell language clean. (It might even be a good idea with respect to the compiler implementation since it removes checking against unsafe IO behavior from the compiler -- just a thought) Haskell used to do this prior to Haskell 1.3. We moved away from it for two reasons: 1) It was notoriously hard to write correct code using the old interface because you had to reason carefully about the order of evaluation of pure Haskell code. 2) As specified, it gave us a fixed language in which to express commands and responses. For example, there was no command to open a network connection or to draw a circle on a window and no response to say 'the result is this COM object'. IMHO, Haskell would be an interesting but useless academic toy without the ability to do things like that. The first might have been fixable (e.g., Clean provides a non-monadic alternative). The second might also be fixable but I doubt that any useful solution would avoid the cleanliness and safety issues you refer to. The sad truth is that as soon as you connect to a world that is not strongly typed (and uses a different type system anyway) and doesn't perform any runtime checks to compensate, you get corrupted by the a bunch of safety issues. -- Alastair Reid www.haskell-consulting.com ___ Haskell mailing list [EMAIL PROTECTED] http://www.haskell.org/mailman/listinfo/haskell
Re: IO behaves oddly if used nested
I think it's wrong. The return type of IO should be discarded. I don't follow. I thought the question was 'what should this print?' not 'what is its type?' Even if it isn't, it doesn't make sense for IO to be in Show. The general policy for Haskell 98 libraries is that if you define a type, you should define a Show instance even if it isn't possible to provide much information. Thus, we have Show instances for - and IO and, in the Haskell extension libraries, we have Show instances for IORef, MVar, etc. It's a bit of a delicate balance. On the one hand, it lets us derive or write a Show instance for any new type constructor that the user might define or any type that might occur in their program. This makes teaching and some forms of debugging easier. On the other hand, if you're not using it for debugging purposes, you might like the typechecker to detect that you're doing something silly just as it would if you wrote 'foo == (\x - x)' or 'sin 1'. But it isn't quite as bad as equality on functions would be since such a test would either fail to terminate in many cases or would have to return an incorrect answer. Overall, I think the balance is about right. Providing Show instances for IO doesn't lead to runtime problems and it doesn't break the desirable 'read . show = id' property since there is no Read instance. Of course, it might be nice if people could choose for themselves whether they get Show instances for IO and -. The simplest way to do this would be to not provide instances in Prelude and have them explicitly import the module if they need it. Haskell compilers could perhaps implicitly import this module if the compiler is in 'Haskell 98' mode. -- Alastair Reidwww.haskell-consulting.com ___ Haskell mailing list [EMAIL PROTECTED] http://www.haskell.org/mailman/listinfo/haskell
RE: IO behaves oddly if used nested
Alastair Reid writes: ... Thus, we have Show instances for - and IO ... Actually, you have to explicitly import Text.Show.Functions to get the Show instance for (-). Cheers, Simon ___ Haskell mailing list [EMAIL PROTECTED] http://www.haskell.org/mailman/listinfo/haskell
Re: Feature-request (was: Re: IO behaves oddly if used nested)
Simon Marlow [EMAIL PROTECTED] writes: You don't need to know :-) It's actually in the base package, but GHCi knows about all hierarchical libraries without having to specify any extra command-line arguments. Ai! And here I've been doing :set -package all this time. -kzm -- If I haven't seen further, it is by standing in the footprints of giants ___ Haskell mailing list [EMAIL PROTECTED] http://www.haskell.org/mailman/listinfo/haskell
Re: IO behaves oddly if used nested
What about this program: main :: IO () main = putStr (show (putStr Hello World!)) Am I the only one who feels that there is some conceptual _wrongness_ about Hugs responding with IO action? I think it is exactly right. Having it print Hello World would clearly be wrong since it confuses values (like strings) with computations that produce strings. One way to understand the difference is to think of the difference between you sending an order to a shop for them to send you a pair of socks and them actually sending you the socks. The order is a computation which you can photocopy, file in your in tray, lose, etc. The sending of the socks is an action described by the order. What would you like to see in this case? In the sock example, I might like to see: Order for 1 pair red socks from blaat blaat But monads are often opaque datatypes so the best I might be able to get is: Order for 0 or more things (Monads are often opaque datatypes because they often involve function types and function types are opaque.) Another question with a trivial answer, what is the result of: main :: IO (IO ()) main = return (putStr Hello World!) It is a computation which, if executed, will print Hello World Clearly it also shows the relation between IO and chosen evaluation strategy. This isn't clear to me at all - can you explain further? -- Alastair ___ Haskell mailing list [EMAIL PROTECTED] http://www.haskell.org/mailman/listinfo/haskell
Re: IO behaves oddly if used nested
Alastair Reid wrote: Another question with a trivial answer, what is the result of: main :: IO (IO ()) main = return (putStr Hello World!) It is a computation which, if executed, will print Hello World Clearly it also shows the relation between IO and chosen evaluation strategy. This isn't clear to me at all - can you explain further? Is it even type correct with main :: IO (IO ()) If it is, it shouldn't be. It makes no sense. The value computed by the top level IO action should have some consumer. Sensible types for the consumer (which in some sense is the OS) are () or some exit code. -- Lennart ___ Haskell mailing list [EMAIL PROTECTED] http://www.haskell.org/mailman/listinfo/haskell
Re: IO behaves oddly if used nested
On Thu, 02 Oct 2003 12:47:15 +0200 Lennart Augustsson [EMAIL PROTECTED] wrote: Alastair Reid wrote: Another question with a trivial answer, what is the result of: main :: IO (IO ()) main = return (putStr Hello World!) It is a computation which, if executed, will print Hello World Clearly it also shows the relation between IO and chosen evaluation strategy. This isn't clear to me at all - can you explain further? Is it even type correct with main :: IO (IO ()) If it is, it shouldn't be. It makes no sense. The value computed by the top level IO action should have some consumer. Sensible types for the consumer (which in some sense is the OS) are () or some exit code. If I'm not mistaken, the Report restricts main's type to be, at least, IO a. Anyways, it's perfectly sensible to return anything. The RTS simply discards it. The above example as an entire program is an IO action that returns an IO action that is discarded by the RTS. I.e. the program doesn't do anything. Neither example is odd behavior, unless you consider Hugs providing a perfectly reasonable instance of Show for IO a odd. ___ Haskell mailing list [EMAIL PROTECTED] http://www.haskell.org/mailman/listinfo/haskell
Re: IO behaves oddly if used nested
Derek Elkins wrote: If I'm not mistaken, the Report restricts main's type to be, at least, IO a. Anyways, it's perfectly sensible to return anything. The RTS simply discards it. The above example as an entire program is an IO action that returns an IO action that is discarded by the RTS. You're right, the report says that the value of the IO is discarded (nd it can be of any type. While I don't think this is the best choice of type for main, it does make the described behaviour perfectly sensible. -- Lennart ___ Haskell mailing list [EMAIL PROTECTED] http://www.haskell.org/mailman/listinfo/haskell
Re: IO behaves oddly if used nested
G'day all. Alastair Reid [EMAIL PROTECTED] wrote: I think it is exactly right. I think it's wrong. The return type of IO should be discarded. Even if it isn't, it doesn't make sense for IO to be in Show. Cheers, Andrew Bromage ___ Haskell mailing list [EMAIL PROTECTED] http://www.haskell.org/mailman/listinfo/haskell