Graham Klyne wrote:
I agree with the surface points you make. It's easy enough to fix the problem once you realize it's there. (In my own "real" program, I moved the hClose, which meant I had to pass the handle out of the function which opened it.)
The underlying thrust of my post was that I thought pure functional languages, like Haskell, were supposed to help one avoid such traps by ensuring the messy dependencies on ordering didn't arise in the first place.
Unfortunately, unless you also have a pure functional operating system, the ordering of I/O operations matters ;) As things stand, I don't think I could begin articulate a reliable
set of rules for avoiding such problems, short of something like "use only strict functions in monad-chains". I'm hoping the Haskell community has some experience with this kind of issue to offer some more helpful advice, or even tools to detect unsafe combinations. Maybe a discussion of safe programming patterns would be a useful interim step?
(e.g. Ketil Z. Malde's suggestion of renaming the function to hUnsafeGetContents maybe a small step in the right direction?)
Well, it is implemented using unsafeInterleaveIO; I'm not sure why lazy I/O generally is considered unsafe but the specific cases of readFile, hGetContents etc aren't.
Thinking some more... I'm reminded of some discussions I had a few years ago about the timing of calls to Java finalizers, and problems this could cause for network I/O programs because using finalizers to close network sockets would lead to unexpected resource problems. The only reliable solution was to always close the sockets explicitly when done. With Java, coming from C/C++, it was possible to get into a mindset that automatic memory management also meant automatic management of all resources, including all those that weren't directly visible to the programmer. Maybe there's a similar trap for the unware in Haskell?
More generally, the concept of "visible" semantics depends upon your (highly subjective) definition of "visible". I'm reminded of a recent BugTraq post regarding wiping sensitive information from memory; the code was basically: char password[...]; read_password(password); do_something(password); memset(password, 0, sizeof(password)); return; The compiler inlined the memset(), then noted that the contents of the password array weren't used after the overwrite, so it optimised the overwrite away. -- Glynn Clements <glynn.clements@virgin.net>