Hacker News new | past | comments | ask | show | jobs | submit login

I don't know what that word means, to me it has always seemed a bit silly. The whole point of software is its side effects, you will always have some. So instead of admitting that, you do a little dance to pretend side effects don't really happen in your program. It's very strange to me.

On the contrary, the whole point of software is its effects. They are only side effects if you're not able to encode the effects in the type of the function. Haskell doesn't treat effects as less important, it treats them as more important by allowing (and, as a consequence, forcing) you to reason about them explicitly in compiler-checked ways. This lets you do great things like have STM transactions with a guarantee that retrying them won't screw anything up. It also lets you define your own kinds of effects that you want to track and plumb them through your system in a coherent way. And it lets you write code that doesn't care what kind of effects it's working with, and yet which can still be used in contexts like STM that need to apply restrictions.

What I'm not liking about this line of reasoning is that in practice Haskell seems to not go far enough[1].

Yes, you have to declare your effects. In practice that means that most of your code returns IO, and isn't constrained anymore. I don't know if this is a library feature, or an essential feature of the language[2], but it would be very interesting for example to put a GUI together by computing events in functions that returned an "Event" monad, widgets in functions that returned a "GUI" monad, database access in functions in a "DB" monad, etc. Instead, all of those operate on IO.

[1] A completely subjective assessment. [2] I've though for a short while on how to code that, but didn't got any idea I liked.

It's not clear that that's the distinction that makes the most sense. I think typing based on capability makes more sense than typing based on purpose. So you would have a type like FSRead and FSWrite which could only read and write from the filesystem, for example. (Ideally with a nice way to combine the two!)

Of course, you can do this as a library. In fact, this is an example use case[1] for Safe Haskell which also prevents people from circumventing your types with unsafePerformIO and friends.

Moreover, some existing libraries already take similar approaches. FRP libraries extract reactive systems (like events but also continuously changing signals) into their own types. A button gives you a stream of type Event () rather than an explicit callback system using the IO type. Check out reactive-banana[2] (my favorite FRP library from the current crop) for a nice example.

Similarly, people use custom monads to ensure things get initialized correctly, which has a similar effect to what you're talking about. The Haskell DevIL bindings for Repa[3] come to mind because they have an IL type which lets you load images and ensures the image loader is initialized correctly exactly once.

Sure, in the end, everything will need to be threaded through IO and main to actually run, but you can—and people do—make your intermediate APIs safer by creating additional distinctions between effects.

[1]: http://www.haskell.org/ghc/docs/7.8.3/html/users_guide/safe-...

[2]: http://hackage.haskell.org/package/reactive-banana

[3]: http://hackage.haskell.org/package/repa-devil

That's great to know. Looks like I have a ton of stuff to read now.

I think there's quite a bit more of this going on than you seem to be aware of. In addition to the things mentioned in a few siblings to this comment, there is STM for software transactional memory, several different DB monads provided by persistent, Query and Update in atomic-state, Hander and Widget in yesod, Sh in shelly... In my experience, very little of my code runs in an unadorned IO monad. Some of these have holes punched in them with liftIO to let me run arbitrary IO - whether that's appropriate depends on the particular context.

I'm currently working on a UI library similar to what you describe. When you've got a situation where "everything just ends up in IO" you probably just have an early design iteration on that space of possible libraries.

Generally the IO monad means "FFI". So in that sense it is very distinctive. The reason they are all lumped into IO is because they all involve FFI.

The thing is IO is generic. So IO (Db) and IO (Gui) are different things.

Yes, one thing I would like to see in Haskell is more monads based on IO, but specialized to deal with specific kinds of side effects.

An example of this(not a very good one I'm afraid) is the X monad.

The IO Sin Bin seems to be about the same thing that you're concerned about.


I wrote about this in another comment a bit, but I'll try here to be a bit more florid.

The best way to understand IO is to think about working with pure functions in an impure language. Let's say I've given you a promised-pure function which emits commands (re: the "Command pattern" if that's the way you want to see it) and you operate them using side effects. This is a massive inversion of control issue of course, but you can see how it might work.

Further, you might understand that your job is easier due to the purity of the command-emitting function. You explicitly give it all of the inputs you desire and operate it as needed. For instance, you can perhaps run it forwards and backwards as desired. Or weave it in with another "thread" I parallel knowing that only you must handle races and shared memory—the threads are pure.

Finally, you might understand that the risk of bad programming is borne on your shoulders primarily—side effects are complex and you're the only one handling them.

In Haskell, "you" are the RTS and the pure threads are Haskell programs. The IO monad is nothing more than what it feels like to be "inside" a useful kind of command pattern. Finally, we compartmentalize all side-effects into the RTS so that we only have to get them right once.

The same way you pretend to have infinite memory by using a garbage collector.

So, speaking as someone who loves functional programming but finds a lot of the flap surrounding the recent popularity of Haskell to be tiresome (just the flap, the language is great), I see it this way:

Purity makes it easier to reason about the semantics of your code. This isn't about parallelism, it's about concurrency, including single-threaded concurrency. Case in point, I recently spent quite a while scratching my head over a bug that happened because someone else had written some code to mutate a piece of shared state when I wasn't expecting it.

But the pure functional programming model is a very high level of abstraction (deep down, every interesting thing in computing is a state machine), and it has a tendency to leak like mad. One such case where it does so is I/O. In fact, you can't even do I/O in a 100% pure language - and that's what the I/O monad is really about; it's punching a hole in the language in order to let the big bad ephemeral outside world in. But in a controlled manner, so that the language's fundamental ethos of purity can be maintained, which in turn makes its laziness manageable. In short, the deeper downer reason why Haskell loves its I/O monad so much is because without it the language would be fairly useless. Anyone who tells you the I/O monad's really about making I/O concurrency headaches less of a hassle has been doing more blog-reading than programming.

So why preserve the illusion? Well, ghettoizing all things stateful lets you take advantage of pure semantics everywhere else in your code, which theoretically makes it easier to reason about and maintain.

As for monads themselves, IMHO they're kind of overblown. It's just another design pattern, akin to Visitor or Strategy or Decorator, only functional instead of object-oriented. Super-useful, applicable in all sorts of circumstances, and easily worth knowing. And, just like object-oriented design patterns, easy to make sound way more complicated than it is if you try to explain the idea to someone else without having fully digested it yourself first.

It is strange to you because it is strange period. It is also a strawman that people like to present for dismissing haskell without learning about it. Haskell does not do anything of the sort. This is why haskellers find the concept every bit as strange as you do. Haskell makes side effects first class. You can pass around side effects and manipulate them just as you would any other value.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact