Departmental restriction against unsafePerformIO
Asked Answered
B

5

6

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.

Bish answered 8/10, 2010 at 18:16 Comment(2)
Wow, the idea of an official policy to explicitly prohibit using unsafePerformIO strikes me as worrying on many levels. I'm not sure which is worse--completely prohibiting a tool useful for FFI, debugging, etc., or that something has presumably motivated this policy...Bert
Your department should have code reviews instead. If someone new to Haskell use unsafePerformIO to cut corners, the reviewer will notice it, and you can fix it together. Rules are stupid. Communication is smart.Copland
D
13

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.

Ding answered 8/10, 2010 at 19:22 Comment(4)
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.Valeriavalerian
@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?Ding
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.Valeriavalerian
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?Ding
B
12

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.

Bert answered 8/10, 2010 at 19:33 Comment(1)
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.Valeriavalerian
W
7

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.

Woodchuck answered 8/10, 2010 at 21:4 Comment(0)
V
4

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.

Valeriavalerian answered 13/10, 2010 at 22:24 Comment(1)
You may not use unsafe functions directly, but if you use Data.Dynamic, the ST monad, etc then you know that they have legitimate uses and not just in FFI code.Ulphia
C
3

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.

Cthrine answered 9/10, 2010 at 0:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.