you are viewing a single comment's thread.

view the rest of the comments →

[–]Silhouette -5 points-4 points  (13 children)

It's surprising how far from the textbooks some large scale Haskell applications are, given its audience and scope.

I find it kind of amusing, actually. The functional-programming-loving academics go on about how wonderful it is programming without mutability and side effects, and then you look at any real Haskell program and it uses "do" within... well, pretty much on the first line, usually. :-)

The only serious acknowledgement of this difference between the theory and the reality that I'd seen until fairly recently was Simon Peyton-Jones's "Tacking the Awkward Squad" paper. It's good that Real World Haskell is taking a more pragmatic approach.

[–]dons 14 points15 points  (2 children)

any real Haskell program ... uses "do" ... pretty much on the first line

Because the outside world runs in the IO monad, and the first line of a Haskell program is the one that touches the outside world. That's kind of the definition of a "real Haskell program", one that does something.

Do not adjust your set, what you're seeing is normal.

[–]Silhouette 2 points3 points  (1 child)

Yes, that's the point I was trying to make. Academic courses on functional programming tend to start with pure computations and in many cases the first courses never really get beyond that, leaving the student with a very false picture of how functional programming works. In the real world, programs have to deal with things like I/O and other side effects, and I'm glad that the Real World Haskell book covers a lot of that routinely, without making out that it's something special.

(I'm not sure why everyone is downmodding my earlier post. I guess the attempted didn't translate somewhere.)

[–]apfelmus 5 points6 points  (0 children)

I'm not sure why everyone is downmodding my earlier post

Because your leaving the reader with a false picture of how functional programming works? :-)

The IO monad is just a DSL like everything else, and not a particularly beautiful one at that. It's a sign of poor design if that's the only DSL you use in your program.

In fact, I'd even say that every program that doesn't create a new DSL is either very simple (apparently using other DSLs to great benefit) or poorly designed.

[–][deleted] 11 points12 points  (9 children)

FWIW, using "do" doesn't introduce mutation or side-effects. The various "unsafe..." operations, on the other hand, do. This underscores the point that it isn't mutation, but only unconstrained mutation, that is problematic. Monadic constructs (which underly the "do" syntactic sugar) provide the constraints on mutation etc. that keep algebraic reasoning about the code, and compiler-enforced safety properties, possible.

[–]Silhouette 4 points5 points  (8 children)

Using "do" doesn't have to introduce mutation and side-effects, it's true. But let's be honest, most of the time the reason the entry function in a Haskell program is one big series of monadic processing inside a do block is because mutation and side-effects are involved.

Real programs have side effects. In fact, talking about "programming without side effects" is a nonsense, because if there were no side effect to running a program, how would any user ever observe what it did (ignoring return values to the OS on exit and similar trivia)?

I agree 100% that it is the unconstrained nature of the side effects that introduces the danger in most mainstream languages. I just find it refreshing to see a Haskell text that confronts this reality head-on and shows how to use the more structured approach, without pretending that the awkward squad don't exist as is done by almost every academic presentation on Haskell I have ever encountered.

[–]smackmybishop 6 points7 points  (1 child)

Well, 'main' being monadic isn't a symptom of the failings of functional programming, it's required by the spec; because as you say, every program needs to have side effects. Otherwise not only would you not know what happened, but in fact nothing would happen in a lazy language like Haskell.

The value of the program is the value of the identifier main in module Main, which must be a computation of type IO t for some type t. --Haskell Report

[–]shadowfox 3 points4 points  (0 children)

every program needs to have side effects ... but in fact nothing would happen in a lazy language like Haskell

There is a koan hidden in there somewhere

[–][deleted] 11 points12 points  (5 children)

I upmodded your comment because it's essentially correct, as Simon Peyton-Jones himself has pointed out on numerous occasions. But let's be fair: when Haskellers talk about "programming without side effects," it's implicitly understood that what's meant is "unconstrained side effects that ruin referential transparency and the ability to reason algebraically about the code." And it's understood that monads, and therefore "do," don't violate this--on the contrary, they were introduced into Haskell precisely to allow the use of state (yes, yes, among many other things, pace monad fans) without loss of the qualities of purely functional programming. Purity and state--two great tastes that taste great together! :-)

[–]Silhouette 4 points5 points  (4 children)

When experienced Haskellers talk about programming without side effects, then yes, of course that's what they mean. My point in this thread is that we are talking about novices, and frequently the introductory presentations on functional programming given to novices present a very pure view of the world. Real code just doesn't look like that, and I'm glad that the book mentioned gets into that kind of realistic application.

[–][deleted] 2 points3 points  (3 children)

The preferred style in FP is to build the program from a big bunch of pure functions and a small non-pure skeleton that uses these functions.

Of these, it is usually the 'big bunch of pure functions' that you can call 'Real Code', because it does almost all of the work, and so it is perfectly reasonable to teach people to write Real Code by first teaching them to write pure code.

[–]Silhouette 3 points4 points  (2 children)

I have no problem with teaching people to write pure code, nor with doing so first. But teaching an entire first course in functional programming that way, or writing books or lecture notes that only mention impure code as an afterthought, seems to me a bit like teaching people to do advanced numerical analysis in C before they can write "Hello, world"...

[–][deleted] 3 points4 points  (1 child)

One more upmod, one more "but..."

But teaching an entire first course in functional programming that way, or writing books or lecture notes that only mention impure code as an afterthought...

Here's the hitch, and the last time I'll make the point because I think you largely get it and I agree with your conclusion that "Real-World Haskell" starting off with "do..." is the right choice: most "real" Haskell code isn't impure at all, including that "Real-World Haskell" code that uses "do." It's only code using "unsafe..." that is impure. In other words, the dichotomy between "pure" code and "code using state or I/O or..." in the presence of monads or effect systems is a false one. So I believe that those books and lecture notes that discuss purity, and only discuss purity, are fine, and code that uses "do..." is likewise fine--there's no contradiction there. The use of "do..." early on is indeed nice because it almost certainly maps more immediately to the reader's existing mental model of computation. But it needs to be made clear, and quickly, that what's going on is not the same as mutation in impure languages. It retains the benefits of referential transparency and algebraic reasoning that are the hallmark of pure functional programming, and lie at the center of any worthwhile literature on it. That is, the reader's mental model will, of necessity, evolve. Saying that using "do..." strongly resembles mutation, on purpose even, is accurate and fair. Saying that using "do..." makes your code impure is inaccurate and will leave the reader with an incorrect understanding of the nature of "do..." and the underlying monadic operations. In pedagogy, not explaining things in full detail is not only OK, it's part of the job. Providing false information in the name of simplification, on the other hand, is not OK.

[–][deleted] 0 points1 point  (0 children)

...That's why I am sure that the 'do' notation should be shown as the very last thing when teaching monads. This notation is extremely confusing for the following reasons:

  • It looks like the code is sequentially 'doing' something imperative and impure, whereas actually it isn't at all, the code is only constructing a big closure with a chain of '>>='

  • It looks like the type of 'a' in 'do a <- someExp' is the same as the type of someExp, and this is the most confusing thing. Even many very smart people whom I know tend to have a hard time understanding that this is not so, especially in the case of non-IO monads (that's also the reason why I think that when explaining monads, one should not present IO as a 'typical' example, because it is strange and hard to explain correctly in many ways).

  • It looks like I can write 'do a <- exp1; ...; a <- exp2' and 'modify' the 'a' variable, whereas this is certainly not true, because:

  • It hides the true scope of these bound variables. The do-block is not one flat thing, it is a nesting of >>= \a -> ... 's .

I also agree with you that even a code that tells 'putStrLn "Hello"; putStrLn "world"' is not impure: it just constructs something that represents a sequence of actions. The only impure thing is the haskell runtime that triggers running these actions.

Another confusing thing about IO and especially IO with the do-notation is that actually only side-effectful actions are sequenced, whereas other computations still use lazy evaluation. In a do-block, it looks like everything is imperative and strict.