Live data from Hacker News

IO Monad Considered Harmful

blog.jle.im

21–30 of 75 posts

Re: IO Monad Considered Harmful

#21

I've been writing Haskell code for years, and this is one of the most singularly useful articles I've read on it. I've certainly found the proliferation of "monad tutorials" and "learn this so you can print something" guides obnoxious, but it never occurred to me that IO really could be taught completely separately from monads. Here's the outline of what the middle of a tutorial could look like, following this advice…

I think your suggestion is a relative improvement over most tutorials. But it's still unmanageably hard.

"Here's how to read something and then print that: >>=". Sure, like `getLine >>= putStrLn`. Technically speaking, that's correct. But this doesn't generalize at all.

Maybe I want to greet the user by name. How do I read the name, prepend "Hello, " to it, and then print that? This is a totally natural thing to want to do, and is very straightforward in Java. But our Haskell newbie hits a wall - none of your cases cover it!

Here's how I might write that today, without using do notation: `getLine >>= (return . (++) "Hello, ") >>= putStrLn`. Look at that - it's totally nuts, and I would not attempt to explain that to a Haskell neophyte.

Using do-notation, you can make things nicer:

    do
     name 
but this introduces a bunch of new syntax to learn: the difference between let-in and let and I don't have a better approach than what you suggest. I suspect that this stuff simply can't be made easy.

Re: IO Monad Considered Harmful

#22
post #14

The thing that makes an 'IO t' data structure different from some arbitrary java bytecode, is that bytecode is an opaque black box, and represents commands that will be executed immediately, whereas an 'IO t' just "describes" the intended action, remains composable and is a first-class value (i.e. data structure ) in your program. It's necessary to draw a distinction between the "runtime" and "evaluation" in Haskell;…

IO t is a black box too. If it's a data structure, it's a crappy one: you can't pick it apart like a list, inspect it, or do anything except sequence it. I didn't follow what you meant about "runtime vs evaluation." To me they seem hopelessly intertwined: if you evaluate head [], you get a runtime error.

It's not a black box in the sense you can move it around, evaluate later, not evaluate at all, etc. There is fundamentally nothing you can inspect about a general IO operation: it's a handle to a computation dealing with the external world.

Re: IO Monad Considered Harmful

#23

I read the article, and it annoyed me, because the rebuttal is obvious and wasn't addressed. The rebuttal is do-notation. The second program you write after Hello World is going to use two IO actions instead of one, and so you need a way to sequence them, and every tutorial is going to do that with do-notation. And suddenly all of the other syntax you learned, like how to declare a variable with let-in, or how arrows…

Yes, you'll need them to build up bigger programs. The point is they aren't fundamental to IO itself.

Re: IO Monad Considered Harmful

#24

I've been writing Haskell code for years, and this is one of the most singularly useful articles I've read on it. I've certainly found the proliferation of "monad tutorials" and "learn this so you can print something" guides obnoxious, but it never occurred to me that IO really could be taught completely separately from monads. Here's the outline of what the middle of a tutorial could look like, following this advice…

I think your suggestion is a relative improvement over most tutorials. But it's still unmanageably hard. "Here's how to read something and then print that: >>=". Sure, like `getLine >>= putStrLn`. Technically speaking, that's correct. But this doesn't generalize at all. Maybe I want to greet the user by name. How do I read the name, prepend "Hello, " to it, and then print that? This is a totally natural thing to want…

> Here's how I might write that today, without using do notation: `getLine >>= (return . (++) "Hello, ") >>= putStrLn`.

Don't write point-free code here. Here's a fairly simple version:

    getLine >>= \name -> putStrLn ("Hello, " ++ name)
The only novel thing there is the >>= operator itself. You don't even have to introduce return there; you can introduce return later.

Re: IO Monad Considered Harmful

#25

I read the article, and it annoyed me, because the rebuttal is obvious and wasn't addressed. The rebuttal is do-notation. The second program you write after Hello World is going to use two IO actions instead of one, and so you need a way to sequence them, and every tutorial is going to do that with do-notation. And suddenly all of the other syntax you learned, like how to declare a variable with let-in, or how arrows…

Yeah, no way around learning do-notation even for the simplest of programs. And do-notation is kind of a DSL with a totally different feel than the rest of the language. So getting started writing even simple toy programs in Haskell requires you to learn two languages. No denying that Haskell is a language with a very steep learning curve. (Or is it a very shallow learning curve? Never understood that metaphor. But you have to learn a lot to get started.)

However it might make sense in a tutorial or course to teach do-notation first as a DSL for IO in order to get started. And only later, when the student have a good understanding of the core language and type system, introduce monads and show how IO is a monad and how do-notation can be used with other monads.

The problem with the much maligned monad tutorials is that the audience typically is beginners who get stuck on on the IO monad and do-notation immediately after writing "hello world". But the tutorials try to explain monads as a general concept, which requires a pretty through understanding of things like higher kinded types and so on. An understanding the newbie getting stuck at "hello world" obviously doesn't have yet. Then the tutorials try to be accessible by using metaphors like a "monad is a specesuit", a "monad is burrito" etc. But knowing that a monad is a buritto doesn't help the newbie who gets inscrutable compile errors after adding a second putStrLn to "hello world".

Re: IO Monad Considered Harmful

#26

Earlier quoted context omitted.

I think your suggestion is a relative improvement over most tutorials. But it's still unmanageably hard. "Here's how to read something and then print that: >>=". Sure, like `getLine >>= putStrLn`. Technically speaking, that's correct. But this doesn't generalize at all. Maybe I want to greet the user by name. How do I read the name, prepend "Hello, " to it, and then print that? This is a totally natural thing to want…

> Here's how I might write that today, without using do notation: `getLine >>= (return . (++) "Hello, ") >>= putStrLn`. Don't write point-free code here. Here's a fairly simple version: getLine >>= \name -> putStrLn ("Hello, " ++ name) The only novel thing there is the >>= operator itself. You don't even have to introduce return there; you can introduce return later.

Thanks, that's much better than what I wrote. Still I think that's a big jump for most programmers - it certainly was for me.

Re: IO Monad Considered Harmful

#27
I've also experienced similar failures. I've tried to learn Haskell several times, and every time I meet the Monad section in the tutorial I lost my concentration. One of my friends who is proficient in Haskell told me to just start coding without trying to understand the advanced concepts, but I wasn't inclined to do that as Monads were hanging over my head, and I really wanted to understand what they are before writing some piece of code.

Funnily enough, things were changed after I learned Rust. (I know I'm saying like a Rust zealot, but it's true!) After using Rust's `Option` type, and realizing its `.map()` and `.and_then()` are similar to `fmap` and `>>=` in Haskell, I finally began to grasp what functors and monads are. And that led me to understand the "implementation details" of Haskell. Before that, I thought `seq` was purely written in Haskell. But it wasn't. It is basically a compiler magic, and GHC specializes `seq` to allow eager evaluation. The same thing goes with the IO type; the monad itself has nothing to do with making I/O be done. I/O is possible solely thanks to the implementation details of the IO type, which resides in GHC. Plus, I can also guess that that part of GHC is probably not written in Haskell, because it is not possible to make side effects in "real" Haskell.

So, Rust was my admirable Haskell teacher. (Thanks, Mozilla!) It's like a stepping stone between the imperative world and the functional world. The funny thing again is, after having fun with Rust for months, now I have less reasons to go back to learn Haskell... Rust is already too powerful, performant, and easy to understand, so Haskell feels less appealing to me. Haskell had been enviable to me in the past, but now it isn't anymore, (un)fortunately.

Re: IO Monad Considered Harmful

#28
Ok, so we can't use the word monad because that's bad apparently. How do we answer "How does Haskell, a FP language where composition is a very important concept, compose IO actions?". Because that's almost always done using monadic actions.

Aside, I agree on the List monad thing. I never ever use the list monad, why would I. But if I'm managing state (i.e. IO, Reader/Writer, Conduit etc), then why wouldn't I use the name of the language construct that Haskell provides to manage state?

Re: IO Monad Considered Harmful

#29
post #2

I wish the author had explained what the difference between using the IO type and using the IO type via the monadic interface looked like. As a Haskell beginner, this article just makes me more confused. Can someone share an example or clarify?

If you are starting Haskell, there is a wonderful little function you can use called `interact`, which has a type `(String -> String) -> IO ()`. This takes a String to String function, and turns it into an IO type that reads from STDIN, runs the function, and writes to STDOUT, lazily. Thus, you can just write `main = interact myFunction` and get everything you need to build complex Unix pipeline utilities without nee…

This sounds amazing. I was one of those beginners who thought I had to learn monads right away to get anything done, became frustrated, and never progressed.

Re: IO Monad Considered Harmful

#30

Earlier quoted context omitted.

I think your suggestion is a relative improvement over most tutorials. But it's still unmanageably hard. "Here's how to read something and then print that: >>=". Sure, like `getLine >>= putStrLn`. Technically speaking, that's correct. But this doesn't generalize at all. Maybe I want to greet the user by name. How do I read the name, prepend "Hello, " to it, and then print that? This is a totally natural thing to want…

> Here's how I might write that today, without using do notation: `getLine >>= (return . (++) "Hello, ") >>= putStrLn`. Don't write point-free code here. Here's a fairly simple version: getLine >>= \name -> putStrLn ("Hello, " ++ name) The only novel thing there is the >>= operator itself. You don't even have to introduce return there; you can introduce return later.

Which can then be made to look exactly like the

  getLine >>= putStrLn
case by doing

  greet name = putStrLn ("Hello, " ++ name)
  main = getLine >>= greet
(Edited to include the definition of main)
Post reply on HN