Re: [Haskell] Top Level TWI's again

[for the third time moving this discussion to cafe] On Tuesday 23 November 2004 20:20, Aaron Denney wrote:
[...about std file handles...] They're wrappers around the integers 0, 1, and 2. The handles could have been implemented to be the same, at each invocation. (I expect they are in most implementations). If we had to make them ourselves, they could be done as:
stdin = makeHandle 0 stdout = makeHandle 1 stderr = makeHandle 2
in absolutely pure Haskell, only the things that manipulate them need be in the IO monad.
If they were simple wrappers around the integers, you'd be right and I couldn't rightfully object to them being top-level values. I don't like it but I have to admit that (although hypothetical) this invalidates the argument I gave against them being top-level things ;-( My only rescue is to shift the blame on the OS. Indeed it is quite debatable whether the raw file descriptor API is a good one. Does it make sense that you can, e.g. swap stdin and stdout? It doesn't seem right to me that file descriptors are reused at all. I think file handles should be completely abstract. Also I would rather have separate types for Input, Output, and RandomAccess Streams, although they might of course share some methods via type classes. I am currently taking a look at Simon Marlow's new_io library that seems to do just that (and a lot more).
Keeping them outside the IO monad, and only accessing them inside -- i.e. the current situation -- would be fine.
I beg to differ. Note, I do not claim they are unsafe.
If it's not unsafe, and it makes for simpler (hence easier to understand, create, debug, and modify) in what sense is it not fine?
I don't buy the "easier to understand, create, debug, and modify" part if it comes to global variables. Not everything that makes things simpler at first, is also good for long-term maintenance, and global variables have accumulated quite a bad reputation in this regard.
They're not mutable in any sense.
Well, a variable in C is not mutable in exactly the same sense: It always refers (="points") to the same piece of memory, whatever value was written to it. Where does that lead us?
A slightly different sense, but I won't quibble much.
Assume a completely type-safe version of C, if that makes it clearer.
It would lead us to being able to have TWIs, only readable or writeable in the IO Monad. Many people don't think that would be such a bad thing. But because of the semantics we expect from IORefs, we can't get them without destroying other properties we want.
a = unsafePerformIO $ newIORef Nothing
Respecting referential integrity would give us the wrong semantics. Adding labels would force the compiler to keep two differently labeled things seperate,
Hmm. That sounds as if the global variables problem is equivalent to the problem of unique name supplies: With global variables (let's say top-level MVars) we can easily implement a unique name supply. And with a unique name supply we could (in principle, at least) define newMVar and newIORef as pure functions taking some unique label as an argument. (QED) I have just taken a closer look at 'linear implicit parameters' because they are supposed to be good for unique name supplies. It seems they are even worse than the 'normal' implicit parameters. Considering the above argument this doesn't surprise me much. That means we are back to using the IO Monad to create unique labels, so all this doesn't gain us anything, at least not on the practical level.
In contrast with IO Handles, there the OS does all the work. If "makeHandle" were exposed to us, It really wouldn't matter whether
handle1 and stdout
handle1 = makeHandle 1 stdout = makeHandle 1
were beta-reduced or not. Either way, the OS naturally handles how we refer to stdout.
I see the difference and I agree. However, you can already do this using the Posix package: makeHandle :: Int -> System.Posix.Fd makeHandle = fromIntegral but note that handleToFd :: Handle -> IO Fd fdToHandle :: Fd -> IO Handle are both in the IO Monad. BTW, the man page for stdin etc. says: "Note that mixing use of FILEs and raw file descriptors can produce unexpected results and should generally be avoided."
(One caveat here -- buffering implemented by the compiler & runtime would make a difference.)
I think this is done neither by compiler nor RTS but by a low-level library. Anyway, it seems to make a difference, as witnessed by the above signatures. Ben

Benjamin Franksen
stdin = makeHandle 0 stdout = makeHandle 1 stderr = makeHandle 2
in absolutely pure Haskell, only the things that manipulate them need be in the IO monad.
If they were simple wrappers around the integers, you'd be right and I couldn't rightfully object to them being top-level values.
They generally have buffers. And stdin remembers when it has been closed and refuses to be read further. -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/
participants (2)
-
Benjamin Franksen
-
Marcin 'Qrczak' Kowalczyk