views:

3194

answers:

5

Haskell is generally referenced as an example of a purely functional language. How can this be justified given the existence of System.IO.Unsafe.unsafePerformIO ?

Edit: I thought with "purely functional" it was meant that it is impossible to introduce impure code into the functional part of the program.

+3  A: 

The language/implementation is purely functional. It includes a couple "escape hatches," which you don't have to use if you don't want to.

Justice
+3  A: 

Unfortunately the language has to do some real world work, and this implies talking with the external environment.

The good thing is that you can (and should) limit the usage of this "out of style" code to few specific well documented portions of your program.

Lorenzo
+53  A: 

The Languages We Call Haskell

unsafePerformIO is part of the Foreign Function Interface specification, not core Haskell 98 specification. It can be used to do local side effects that don't escape some scope, in order to expose a purely functional interface. That is, we use it to hide effects when the type checker can't do it for us (unlike the ST monad, which hides effects with a static guarantee).

To illustrate precisely the multiple languages that we call "Haskell", consider the image below. Each ring corresponds to a specific set of computational features, ordered by safety, and with area correlating to expressive power (i.e. the number of programs you can write if you have that feature).

The language known as Haskell 98 is specified right down in the middle, admitting total and partial functions. Agda (or Epigram), where only total functions are allowed, is even less expressive, but "more pure" and more safe. While Haskell as we use it today includes everything out to the FFI, where unsafePerformIO lives. That is, you can write anything in modern Haskell, though if you use things from the outer rings, it will be harder to establish safety and security guarantees made simple by the inner rings.

alt text

So, Haskell programs are not typically built from 100% referentially transparent code, however, it is the only moderately common language that is pure by default.

Don Stewart
Do I understand you correctly, that people should be more precisely saying "Haskell 98 is a pure functional language" and not "Haskell is a purely functional language"?
Stefan Schmidt
@Stefan Schmidt: more like "Haskell *is* purely functional", but "GHC is an implementation of a super-set of Haskell that includes the Foreign Function Interface which is not purely functional"
yairchu
@Don: Thank you for the interesting graphic!
Stefan Schmidt
I think I like this answer even better
Stefan Schmidt
+19  A: 
Norman Ramsey
http://www.haskell.org/ghc/docs/6.12.2/html/libraries/What is "part of Haskell" in your understanding?
Stefan Schmidt
@StefanSchmidt: Something that has been defined in the language standard. Just because something is part of ghc, it's not part of haskell anymore than anything in gcc is part of the C language.
sepp2k
@Stefan: For questions like this, "Haskell" means the *Haskell 2010* standard, or possibly the *Haskell 98* standard as published by Cambridge University Press in 2003. In other contexts, many programmers use the word "Haskell" to describe whatever GHC happens to implement that week.
Norman Ramsey
+1  A: 

I have a feeling I'll be very unpopular for saying what I'm about to say, but felt I had to respond to some of the (in my opinion mis-) information presented here.

Although it's true that unsafePerformIO was officially added to the language as part of the FFI addendum, the reasons for this are largely historical rather than logical. It existed unofficially and was widely used long before Haskell ever had an FFI. It was never officially part of the main Haskell standard because, as you have observed, it was just too much of an embarrassment. I guess the hope was that it would just go away at some point in the future, somehow. Well that hasn't happened, nor will it in my opinion.

The development of FFI addendum provided a convenient pretext for unsafePerformIO to get snuck in to the official language standard as it probably doesn't seem too bad here, when compared to adding the capability to call foreign (I.E. C) code (where all bets are off regarding statically ensuring purity and type safety anyway). It was also jolly convenient to put it here for what are essentially political reasons. It fostered the myth that Haskell would be pure, if only it wasn't for all that dirty "badly designed" C, or "badly designed" operating systems, or "badly designed" hardware or .. whatever.. It's certainly true that unsafePerformIO is regularly used with FFI related code, but the reasons for this are often more to do with bad design of the FFI and indeed of Haskell itself, not bad design of whatever foreign thing Haskell is trying interface too.

So as Norman Ramsey says, the official position came to be that it was OK to use unsafePerformIO provided certain proof obligations were satisfied by whoever used it (primarily that doing this doesn't invalidate important compiler transformations like inlining and common sub-expression elimination). So far so good, or so one might think. The real kicker is that these proof obligations cannot be satisfied by what is probably the single most common use case for unsafePerformIO, which by my estimate accounts for well over 50% of all the unsafePerformIOs out there in the wild. I'm talking about the appalling idiom known as the "unsafePerformIO hack" which is provably (in fact obviously) completely unsafe (in the presence of inlining and cse) .

I don't really have the time, space or inclination to go into what the "unsafePerformIO hack" is or why it's needed in real IO libraries, but the bottom line is that folk who work on Haskells IO infrastructure are usually "stuck between a rock and a hard place". They can either provide an inherently safe API which has no safe implementation (in Haskell), or they can provide an inherently unsafe API which can be safely implemented, but what they can rarely do is provide safety in both API design and implementation. Judging by the depressing regularity with which the "unsafePerformIO hack" appears in real world code (including the Haskell standard libraries), it seems most choose the former option as the lesser of the two evils, and just hope that the compiler won't muck things up with inlining, cse or any other transformation.

I do wish all this was not so. Unfortunately, it is.

I wanted to upvote your answer, but I think going into what the "unsafePerformIO hack" is or why it's needed in real IO libraries is what makes your answer helpful. Most of your answer is just handwaving.
Wei Hu