
On Tue, Aug 25, 2009 at 2:03 PM, Simon Marlow
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 review. -- Johan