tags:

views:

200

answers:

5

There has been some talk at work about making it a department-wide policy of prohibiting the use of unsafePerformIO and its ilk. Personally, I don't really mind as I've always maintained that if I found myself wanting to use it, it usually meant that I need to rethink my approach.

Does this restriction sound reasonable? I seem to remember reading somewhere that it was included mainly for FFI, but I can't remember where I read that at the moment.

edit: Ok, that's my fault. It wouldn't be restricted where it's reasonably needed, ie. FFI. The point of the policy is more to discourage laziness and code smells.

+8  A: 

A lot of core libraries like ByteString use unsafePerformIO under the hood, for example to customize memory allocation.

When you use such a library, you're trusting that the library author has proven the referential transparency of their exported API, and that any necessary preconditions for the user are documented. Rather than a blanket ban, your department should establish a policy and a review process for making similar assurances internally.

keegan
As one of the authors of `ByteString` I'm not at all convinced that `unsafePerformIO` is a good idea in that library. We have had several cases where the `unsafePerformIO` caused real (subtle) bugs.I think it could be implemented as efficiently without using `unsafePerformIO`. Instead it should use GHC's `MutableByteArray#` type in the `ST` monad.
Duncan Coutts
@dcoutts: Very interesting. Would that expose `ST` to the user and require them to call `runST`, or would you be able to hide that somehow?
keegan
@keegan The external API would be exactly the same, i.e. pure. Internally there would be no IO and no `unsafePerformIO`. Basically where the current code calls `unsafePerformIO`, the new code would call `runST`.
Duncan Coutts
I'm not sure I follow. You'd replace the `ForeignPtr Word8` in `ByteString` with `MutableByteArray# s`? In that case, I'm not sure how you'd deal with the state token `s`. Does it become part of the user-visible `ByteString` type or do you quantify it away somehow?
keegan
+6  A: 

Well, there are valid uses for unsafePerformIO. It's not there just to be decorative, or as a temptation to test your virtue. None of those uses, however, involve adding meaningful side effects to everyday code. Here's a few examples of uses that can potentially be justified, with various degrees of suspicion:

  • Wrapping a function that's impure internally, but has no externally observable side effects. This is the same basic idea as the ST monad, except that here the burden is on the programmer to show that the impurity doesn't "leak".

  • Disguising a function that's deliberately impure in some restricted way. For instance, write-only impurity looks the same as total purity "from the inside", since there's no way to observe the output that's produced. This can be useful for some kinds of logging or debugging, where you explicitly don't want the consistency and well-defined ordering required by the IO monad. An example of this is Debug.Trace.trace, which I sometimes refer to as unsafePerformPrintfDebugging.

  • Introspection on pure computations, producing a pure result. A classic example is something like the unambiguous choice operator, which can run two equivalent pure functions in parallel in order to get an answer quicker.

  • Internally unobservable breaking of referential transparency, such as introducing nondeterminism when initializing data. As long as each impure function is evaluated only once, referential transparency will be effectively preserved during any single run of the program, even if the same faux-pure function called with the same arguments gives different results on different runs.

The important thing to note about all of the above is that the resulting impurity is carefully controlled and limited in scope. Given a more fine-grained system of controlling side-effects than the all-purpose IO monad, these would all be obvious candidates for slicing off bits of semi-purity, much like the controlled mutable state in the aforementioned ST monad.


Post scriptum: If a hard-line stance against any non-required use of unsafePerformIO is being considered, I strongly encourage extending the prohibition to include unsafeInterleaveIO and any functions that allow observation of its behavior. It's at least as sketchy as some of the unsafePerformIO examples I listed above, if you ask me.

camccann
You just need to realise when you use it for things like trace, or unanb that you are no longer programming in Haskell. You are writing a new "primitive" that cannot be written in Haskell and exporting it to the nice clean Haskell world.
Duncan Coutts
+2  A: 

unsafePerformIO is the runST of the IO monad. It is sometimes essential. However, unlike runST, the compiler cannot check that you are preserving referential transparency.

So if you use it, the programmer has a burden to explain why the use is safe. It shouldn't be banned, it should be accompanied with evidence.

Don Stewart
+2  A: 

Outlawing unsafePerformIO is a terrible idea, because it effectively locks code into the IO monad: for example, a c library binding will almost always be in the IO monad - however, using unsafePerformIO a higher-level purely functional library can be built on top of it.

Arguably, unsafePerformIO reflects the compromise between the highly stateful model of the personal computer and the pure, stateless model of haskell; even a function call is a stateful from the computer's point of view since it requires pushing arguments onto a stack, messing with registers, etc., but the usage is based on the knowledge that these operations do in fact compose functionally.

Frederic Koehler
+1  A: 

Outlawing unsafePerformIO in "application" code is an excellent idea. In my opinion there is no excuse for unsafePerformIO in normal code and in my experience it is not needed. It is really not part of the language so you are not really programming in Haskell any more if you use it. How do you know what it even means?

On the other hand, using unsafePerformIO in an FFI binding is reasonable if you know what you are doing.

Duncan Coutts