Playing unsafe haskell

Well, at first I thought I’d call this post “Doing Haskell without a condom” but decided it was probably best not to. :-) I can’t really take full credit for this post, I’ve stolen almost all of it from emails after I posted a question on haskell-cafe. All praise should go to the helpful people there!

What I wanted to do was model a finite stream of external events as a list and then map a function on that list. The processing of an event should happen as soon as an event is encoutered (one could say that the list of events should be lazy). Due to the wording “external events” the solution will have to be in the IO monad. It’s all a bit vague at this point, so let’s take it down to a more concrete level and then at the end make the solution more generic again. Let’s read characters until we read a q and print them.

First attempt, infinite list

Let’s start with an infinite list:

Then I can map printChar like this:

The result is almost what I want, each character is printed immediately. The only problem is that there is no end to the stream of characters. This means that the processing never terminates, that’s not a good thing.

Recursive (non-)solution

Solving the problem with the infinite list is easy if we do it recursively:

This is easy to make more generic, but I expressively wanted to map a function on a list. So a recursive solution won’t cut it this time. However, the same pattern can be used to create a list inside the IO monad:

Now mapping putChar on the result of this doesn’t quite produce the desired result.

It doesn’t print characters as soon as they are entered. That is, it’s not lazy.

Adding laziness

Monads have an ordering effect on things. Since listChar executes in the IO monad, and the result then is passed to putChar, which also executes in the IO monad, we get a strict ordering between the two functions. What we need is to somehow interleave the IO in listChars with the IO in putChar. Put interleave into Hoogle and you’re pointed to unsafeInterleaveIO in System.IO.Unsafe. We can put it to use like this:

And now we get the required behaviour. Great!

IO inside on the haskell wiki has the following comment on unsafeInterleaveIO:

But there is an even stranger operation called ‘unsafeInterleaveIO’ that gets the “official baton”, makes its own pirate copy, and then runs an “illegal” relay-race in parallel with the main one! I can’t talk further about its behavior without causing grief and indignation, so it’s no surprise that this operation is widely used in countries that are hotbeds of software piracy such as Russia and China! ;) Don’t even ask me - I won’t say anything more about this dirty trick I use all the time ;)

So, I suppose it’s worth the effort to think through just how we put it to use here. Basically one can say that unsafeInterleaveIO creates a second IO monad, and in order to keep some level of determinism in the program the use of the new one should be kept separate from the use of the main IO monad, i.e. the one that main executes in. In this case the main IO monad is used to print characters to stdout while the one created by unsafeInterleaveIO reads characters from stdin. According to my limited understanding of IO in haskell this should mean we are safe, the two IO monads run no risk of ever “crossing”.

Another solution

Joachim Breitner suggested another, very cute solution based on cutting the infinite list short.

Generalisations

Recursive solution:

Joachim’s solution:

Could ListT be used in a solution?

This was one of my first thoughts. I mean what I want is the combination of two monads, List and IO. So, ListT IO Char should offer a solution right?

In theory it could! In practice it doesn’t! Apparantly ListT imposes unnecessary strictness. That means that it doesn’t offer a solution in this case.

⟸ Some of my thoughts on DRM... Epilicious 0.10.2 ⟹

Alan Falloon

If your event stream is actually the characters of stdin, then getContents already hides the details of the unsafeInterleaveIO for you. As in:

However, understanding the internals makes it possible to generalize this approach to one where the event stream contains richer data (such as GUI events).

Perhaps there is more that can be learned from FRP (functional reactive programming)

Magnus

Alan,

That’s a very good point. I’ve found that quite a few standard functions are “lazy enough”, just like getContents.

Thanks for the link to FRP!

Leave a comment