Re: [Haskell-cafe] Haskell-Cafe Digest, Vol 93, Issue 58

An earlier note on students reactions to the imperative style forced on them by some Haskell libraries ("do ...") is interesting, and seems similar to an observation in a project I was developing for students; making a version of a simple lab from previous SML assignment. It uses a dictionary to do some statistics for a text analysis, but since the dictionary is read at a leaf node of the analysis algorithm, that function must be IO(), and thus the analysis using it also, ... etc. all the way up to the top. So the implication of the rules: 1) all IO must start from the top level, and there is only one IO 2) you cannot extract anything from an IO Seems to be that the whole program structure becomes a series of do... blocks, which is basically a sequential imperative looking style. The general advice of "Strive to keep as much of the program pure as possible" thus seems difficult. An option I suppose would be to read the dictionary at the top level, and then pass it all the way down to the analysis routine that uses it, but that exposes the details of how the analysis is done, and couples the top and bottom levels of the previously modular functions. While this is all logical and understandable, it is quite different than how I did this in SML; where I could encapsulate the IO in the analysis function. It was a local secret of the analysis what data it needed, and where it came from. Note that (of course...) if the dictionary was static and an internal data structure, then this would all go away. It is interesting to me (and curious at first) that one could not somehow treat a "constant" data definition file resource in a more encapsulated manner, and not have it ripple all the way up through the program because it was "impure" once converted to an external definition (=IO). My first impulse was to read the dictionary in a do... and then try to extract it and go merrily on, but that won't work - by design of course! Considering something like a properties file in Java, it thus seems like every part of a program wanting to use these, must either be passed some global definition, or become a leaf on a hierarchy of do.. blocks if it does its own IO to read the properties. Anyway, while the more precise treatment and isolation of IO in Haskell seems valuable, it also seems to have a notable impact on the lack of ability to encapsulate and decouple parts of the program, and keep things pure between them. I suppose this is because that by definition, once you have touched IO at any leaf of a program, the whole thing is impure all the way up the functional tree. I rather had the feeling expressed by Robert Harper: " Once you're in the IO monad, you're stuck there forever, and are reduced to Algol-style imperative programming." (http://existentialtype.wordpress.com/2011/05/01/of-course-ml-has-monads/) Just an observation, in case I am missing something - being fairly new to Haskell. I suppose this is just an adjustment to proper treatment of the impurity of IO, but the effect on program structure was not good. :-) -------------------------------------------
-----Original Message----- Subject: Haskell-Cafe Digest, Vol 93, Issue 58 Now, I have a personal pedagogical comment. A few months ago I gave to some 30 students an The results? A true DISASTER! The OpenGL bindings in Haskell are hardly "functional". You make us sweat with generic functional patterns, laziness, exquisite typing, non-determinism monad, parsing tools, etc., and then you throw us into this ugly imperativism !

An option I suppose would be to read the dictionary at the top level, and then pass it all the way down to the analysis routine that uses it, but that exposes the details of how the analysis is done, and couples the top and bottom levels of the previously modular functions.
It would seem to me that having the analysis routine do the I/O itself is more coupling than designing it to be datasource-agnostic! I'd expect it to be much neater to thread the data through the various functions comprising the analysing functions, perhaps monadically, as a part of its design; and then to feed the data in at a single entry point. Thus the entire analysis is pure.

Yes, agree. Thanks. But still this adds a coupling that I did not need in the SML versions. And in this case, the analysis is word oriented, so the algorithm is intrinsically tied to a dictionary. ------------------------------------------- Gregory Guthrie ------------------------------------------
-----Original Message----- From: Arlen Christian Mart Cuss [mailto:celtic@sairyx.org] Sent: Wednesday, June 08, 2011 10:50 PM To: Gregory Guthrie Cc: haskell-cafe@haskell.org Subject: Re: [Haskell-cafe] Haskell-Cafe Digest, Vol 93, Issue 58
An option I suppose would be to read the dictionary at the top level, and then pass it all the way down to the analysis routine that uses it, but that exposes the details of how the analysis is done, and couples the top and bottom levels of the previously modular functions.
It would seem to me that having the analysis routine do the I/O itself is more coupling than designing it to be datasource-agnostic!
I'd expect it to be much neater to thread the data through the various functions comprising the analysing functions, perhaps monadically, as a part of its design; and then to feed the data in at a single entry point. Thus the entire analysis is pure.

But still this adds a coupling that I did not need in the SML versions.
I suppose you could call it a coupling -- but comparing to the MLs, I'd prefer be forced to specify and thread my inputs and outputs (mostly hidden by monads) than to be hit by weak/imperative type variables in other cases.

On Wed, Jun 8, 2011 at 8:17 PM, Gregory Guthrie
An earlier note on students reactions to the imperative style forced on them by some Haskell libraries ("do ...") is interesting, and seems similar to an observation in a project I was developing for students; making a version of a simple lab from previous SML assignment.
It uses a dictionary to do some statistics for a text analysis, but since the dictionary is read at a leaf node of the analysis algorithm, that function must be IO(), and thus the analysis using it also, ... etc. all the way up to the top.
So the implication of the rules: 1) all IO must start from the top level, and there is only one IO 2) you cannot extract anything from an IO
Seems to be that the whole program structure becomes a series of do... blocks, which is basically a sequential imperative looking style. The general advice of "Strive to keep as much of the program pure as possible" thus seems difficult.
You're right, in some ways. But it is not difficult to stay out of IO. Just don't use the (IO a) type if you don't need to do input and output. I strongly suspect that "you" are starting your program writing process by writing IO actions, which "naturally" leads to writing a tree of IO actions. Start by writing data types and transformation functions instead. "Every" program does three things: take some kind of input, apply a transformation, and yield some kind of output. Strive to define datatypes that capture the program's logic. For example, enumerate your cases in an abstract data type. In this way, you can move the program logic into the pure fragment of the language, as opposed to using explicit if-then-else's in the IO monad.
An option I suppose would be to read the dictionary at the top level, and then pass it all the way down to the analysis routine that uses it, but that exposes the details of how the analysis is done, and couples the top and bottom levels of the previously modular functions.
While this is all logical and understandable, it is quite different than how I did this in SML; where I could encapsulate the IO in the analysis function. It was a local secret of the analysis what data it needed, and where it came from. Note that (of course...) if the dictionary was static and an internal data structure, then this would all go away. It is interesting to me (and curious at first) that one could not somehow treat a "constant" data definition file resource in a more encapsulated manner, and not have it ripple all the way up through the program because it was "impure" once converted to an external definition (=IO). My first impulse was to read the dictionary in a do... and then try to extract it and go merrily on, but that won't work - by design of course!
I don't know how ML handles IO, but Haskell is a lazy language. In order for a value to be computed, some other value has to "request" it. And programs usually have a single entry point -- main :: IO a. So it is going to be the thing that requests computations to be done. On the other hand, it can request a series of IO actions to determine program flow/logic, or it can request pure computations to do the same.

On Jun 8, 2011, at 11:17 PM, Gregory Guthrie wrote:
I rather had the feeling expressed by Robert Harper: " Once you're in the IO monad, you're stuck there forever, and are reduced to Algol-style imperative programming." (http://existentialtype.wordpress.com/2011/05/01/of-course-ml-has-monads/ )
If Algol is as elegant and expressive as IO in Haskell, I'd hardly consider that a problem. In fact, I would seriously consider learning Algol. There seems to be a lot of IO-hate floating around, but in my opinion it is largely undeserved. Even if every function in Haskell had to use IO, Haskell would still be a vastly more expressive language than most. It would essentially be a lazy dialect of ML with different syntax and without parameterized modules (and, in GHC, with a lot of awesome type system extensions). IO has pretty much the same expressiveness as ML. It has higher-order functions, closures, etc. You can build higher level abstractions on top of it, and people do - there are some pretty awesome libraries out there that are nothing more than "glorified" IO. The CHP library, for example, builds a very powerful and very un-IO-like abstraction on top of IO. IO is not such a bad place to be stuck, all things considered. I think there is a tendency to look at IO as bad because pure code is so much better. But it's important to keep things in perspective - the existence of something better doesn't make something bad. IO is still better than the best of most other languages. -- James

I think there is a tendency to look at IO as bad because pure code is so much better. But it's important to keep things in perspective - the existence of something better doesn't make something bad. IO is still better than the best of most other languages.
I have been bashing IO from time to time on lambda-the-ultimate.org (LtU). I can't say I got a lot of response, so I don't think there's a lot of hate around. The thing about IO is that it solves one problem well: interfacing with an imperative sequential world. Old manners like stream handling functions are just plain awkward and need some kind rendezvous; uniqueness typing is fast, composable, but a bit awkward too, and -truth be told- better encapsulated in its own monad probably anyway. There also is certainly a case that banning benign side effects might have a lot more to do with the fact that that is inherently difficult in a lazy pure language (and compiler for that) than that it's so bad for the average programmer. The thing against the IO monad is not that there's anything wrong with it. Just that it kind of locks programmers into a style of programming, I believe, functional programming is just trying to avoid, i.e., imperative programming. The whole point of old-school functional programming was (always) to declaratively build libraries out of combinators which implement a specific purpose. Now, I don't know Haskell that well, but it feels to me that a lot of libraries written are now in a sequential imperative style instead of that combinatorial style. So, I, for one, are not against IO, but consider it something which should be avoided instead of cuddled. Cheers, Marco (Sorry for sending multiple copies James; the woes of gmail... sigh...)

On 10/06/2011, at 1:24 AM, James Cook wrote:
On Jun 8, 2011, at 11:17 PM, Gregory Guthrie wrote:
I rather had the feeling expressed by Robert Harper: " Once you're in the IO monad, you're stuck there forever, and are reduced to Algol-style imperative programming." (http://existentialtype.wordpress.com/2011/05/01/of-course-ml-has-monads/)
The problem is that ML is *also* stuck in the IO monad all the time, it just tries to pretend that it isn't. From the signature of an ML function you have no idea what state changes it might cause. Ironically, as someone who originally loved ML to bits, I've always found I/O code harder to express in SML than in Haskell.

On 11-06-08 11:17 PM, Gregory Guthrie wrote:
So the implication of the rules: 1) all IO must start from the top level, and there is only one IO 2) you cannot extract anything from an IO
Seems to be that the whole program structure becomes a series of do... blocks, which is basically a sequential imperative looking style. The general advice of "Strive to keep as much of the program pure as possible" thus seems difficult.
Suppose you teach Java or C++ or any OOP, and you give out this basic homework to beginners: write a program to take two complex numbers from the user, then print their sum. You are assured to receive such flawed design: class complex { double r, i; public: complex() { cout << "please enter the real part:"; cin >> r; cout << "please enter the imaginary part:"; cin >> i; } } The urge to do I/O at all the wrong places is a born instinct. Humanity strives to organize programs (and articles) by stream of consciousness rather than by separation of concerns. Why do they ask the user in the constructor? Because that's where the editor cursor was when they remembered to ask the user. Similarly in Haskell, why do they read input in the data processing function? Because that's where the editor cursor was when they remembered to read input. This is universal to FP, OOP, IP, any P, with or without language restrictions. For people who organize programs by stream of consciousness, Haskell's separation of IO does not help, but SML's and C++'s non-separation does not help either. Nothing helps. Don't worry about them. For people who organize programs by separation of concerns, such as you and me: Haskell's separation of IO helps. This is what matters.

The urge to do I/O at all the wrong places is a born instinct.
The original poster was well aware how to factor out IO out of his program, as made apparent by his initial comment: "An option I suppose would be to read the dictionary at the top level, and then pass it all the way down to the analysis routine that uses it, but that exposes the details of how the analysis is done, and couples the top and bottom levels of the previously modular functions." His original argument was a genuine observation that IO sometimes seems to be adverse to the loose coupling of modules. Dissing that argument away as: "you are doing IO at the wrong place," doesn't convince me the least. Cheers, Marco

On Mon, Jun 13, 2011 at 4:45 PM, M.C.A. (Marco) Devillers < marco.devillers@gmail.com> wrote:
The urge to do I/O at all the wrong places is a born instinct.
The original poster was well aware how to factor out IO out of his program, as made apparent by his initial comment: "An option I suppose would be to read the dictionary at the top level, and then pass it all the way down to the analysis routine that uses it, but that exposes the details of how the analysis is done, and couples the top and bottom levels of the previously modular functions."
His original argument was a genuine observation that IO sometimes seems to be adverse to the loose coupling of modules. Dissing that argument away as: "you are doing IO at the wrong place," doesn't convince me the least.
There are ways to decouple IO from pure code. I often have a "Main" datatype which constructs a "pure context" (based on command line arguments, files and their contents, etc) which is then "interpreted" by pure code. In other words, this is a pure control structure, which is computed by IO (sometimes in an applicative style, sometimes in a monadic one), and then passed into the pure world for processing. This can be as tightly or loosely coupled as we like, assuming we understand that if a value is even partially based on the results of an IO computation, then IO is going to drive the computation of that value. There's no way around that.
participants (7)
-
Albert Y. C. Lai
-
Alexander Solla
-
Arlen Christian Mart Cuss
-
Gregory Guthrie
-
James Cook
-
M.C.A. (Marco) Devillers
-
Richard O'Keefe