Potential Network SIG

Johan Tibell johan.tibell at gmail.com
Tue Aug 25 16:23:19 EDT 2009

On Tue, Aug 25, 2009 at 2:03 PM, Simon Marlow<marlowsd at gmail.com> wrote:
> On 22/08/2009 05:49, Thomas DuBuisson wrote:
>> 3) Use Bytestrings (and have corrosponding .Lazy modules) for efficiency.
>> As in network-bytestring, any new API should be performance concious
>> enough to avoid String.
> Idealogically speaking, this is not a choice you should make in the network
> library.  The network library should deal with setting up sockets, and
> delegate the actual I/O to the I/O library.
> Right now, that means making Handles from Sockets (which is something the
> current network library provides).  And then you use the bytestring library
> to write bytestrings to the Handle.  In the future we'll have a way to write
> text to a Handle too.
> Now, I wouldn't be surprised if this doesn't cover all the use cases. Maybe
> people want to use the low-level send/recv.  But I expect that for most
> applications, going via Handle will be the right thing, and we should look
> at how to accommodate the other use cases.

In my mind an improved I/O library would look something like this:

> -- At the very bottom is a type class 'RawIO' which represents a
> -- variety of stream-like types.
> class RawIO a where
>     readInto :: Ptr Word8 -> Int -> IO ()
>     write :: ByteString -> IO ()
>     read :: Int -> IO ByteString
>     read n = ByteString.createAndTrim n (\p -> readInto p n)

This definition is very minimal and most likely need to be expanded
with operations such as 'close' and perhaps also 'seek'. The methods
would map to the system calls for e.g. files and sockets. A particular
instance could would exceptions for unsupported methods (e.g. a file
opened as read-only would throw exceptions if 'write' is called).

> -- A simple wrapper for file descriptors.
> data File = File CInt
> instance RawIO File where
>     readInto = cRead
>     write = cWrite
> -- This is only for stream like sockets. Datagram sockets still need to use the lower level API.
> instance RawIO Socket where
>     readInto = cRecv
>     write = send
> -- Assuming 'Buffer' is a mutable byte buffer type.
> -- This is useful for e.g. testing. ByteString could also be
> -- made an instance that throws exceptions for unimplemented
> -- methods (e.g. write in this case).
> instance RawIO Buffer where
>    readInto = readFromBufferInto
>    write = writeToBuffer

We can now layer buffering on top.

> -- Buffers for reading and writing are kept in a data type 'BufferedIO'.
> -- This data type need not be exposed.
> data BufferedIO = forall a. RawIO a => BufferedIO Buffer Buffer a
> instance RawIO BufferedIO where
>     readInto = readFromBufferInto  -- Calls RawIO.readInto if needed
>     write = writeToBuffer  -- Calls RawIO.write if needed
> -- Allocates buffers and returns a BufferedIO
> buffered :: RawIO a => a -> a
> buffered = ...

We might opt for a type class for buffered I/O in case we want to
expose any methods in addition to those exported by RawIO.

We can now layer text I/O on top of buffered I/O:

> class TextIO a where
>     read :: Int -> IO Text
>     write :: Text -> IO ()
>     readLine :: IO Text
> -- To do this efficiently BufferedIO might need to expose its buffer.
> -- Alternatively TextIO can be layered directly on top of RawIO and
> -- manage its own buffers.
> text :: (BufferedIO a, TextIO b) => a -> b
> text = ...

A few people have express interest in discussing this at ICFP. Perhaps
we could draft a proposal and put it on a wiki page for others to

-- Johan

More information about the Libraries mailing list