[Haskell-cafe] Re: [Haskell] Top Level TWI's again

Benjamin Franksen benjamin.franksen at bessy.de
Tue Nov 23 16:58:28 EST 2004


[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


More information about the Haskell-Cafe mailing list