Copyright | Copyright (C) 2004-2011 John Goerzen |
---|---|
License | BSD3 |
Maintainer | John Goerzen <jgoerzen@complete.org> |
Stability | provisional |
Portability | portable |
Safe Haskell | None |
Language | Haskell98 |
Haskell Virtual I/O -- a system to increase the flexibility of input and output in Haskell
Copyright (c) 2004-2005 John Goerzen, jgoerzen@complete.org
HVIO provides the following general features:
- The ability to use a single set of functions on various different types of objects, including standard Handles, in-memory buffers, compressed files, network data streams, etc.
- The ability to transparently add filters to the I/O process. These filters could include things such as character set conversions, compression or decompression of a data stream, and more.
- The ability to define new objects that have the properties of I/O objects and can be used interchangably with them.
- Specification compatibility with, and complete support for, existing I/O on Handles.
- Provide easier unit testing capabilities for I/O actions
HVIO defines several basic type classes that you can use. You will mostly
be interested in HVIO
.
It's trivial to adapt old code to work with HVIO. For instance, consider this example of old and new code:
printMsg :: Handle -> String -> IO () printMsg h msg = hPutStr h ("msg: " ++ msg)
And now, the new way:
printMsg :: HVIO h => h -> String -> IO () printMsg h msg = vPutStr h ("msg: " ++ msg)
There are several points to note about this conversion:
- The new method can still accept a Handle in exactly the same way as the old method. Changing your functions to use HVIO will require no changes from functions that call them with Handles.
- Most "h" functions have equivolent "v" functions that operate on HVIO classes instead of the more specific Handle. The "v" functions behave identically to the "h" functions whenever possible.
- There is no equivolent of "openFile" in any HVIO class. You must create your Handle (or other HVIO object) using normal means. This is because the creation is so different that it cannot be standardized.
In addition to Handle, there are several pre-defined classes for your use.
StreamReader
is a particularly interesting one. At creation time, you pass
it a String. Its contents are read lazily whenever a read call is made. It
can be used, therefore, to implement filters (simply initialize it with the
result from, say, a map over hGetContents from another HVIO object), codecs,
and simple I/O testing. Because it is lazy, it need not hold the entire
string in memory. You can create a StreamReader
with a call to
newStreamReader
.
MemoryBuffer
is a similar class, but with a different purpose. It provides
a full interface like Handle (it implements HVIOReader
, HVIOWriter
,
and HVIOSeeker
). However, it maintains an in-memory buffer with the
contents of the file, rather than an actual on-disk file. You can access
the entire contents of this buffer at any time. This can be quite useful
for testing I/O code, or for cases where existing APIs use I/O, but you
prefer a String representation. You can create a MemoryBuffer
with a call
to newMemoryBuffer
.
Finally, there are pipes. These pipes are analogous to the Unix
pipes that are available from System.Posix, but don't require Unix and work
only in Haskell. When you create a pipe, you actually get two HVIO objects:
a PipeReader
and a PipeWriter
. You must use the PipeWriter
in one
thread and the PipeReader
in another thread. Data that's written to the
PipeWriter
will then be available for reading with the PipeReader
. The
pipes are implemented completely with existing Haskell threading primitives,
and require no special operating system support. Unlike Unix pipes, these
pipes cannot be used across a fork(). Also unlike Unix pipes, these pipes
are portable and interact well with Haskell threads. A new pipe can be created
with a call to newHVIOPipe
.
Together with System.IO.HVFS, this module is part of a complete virtual filesystem solution.
- class Show a => HVIO a where
- vClose :: a -> IO ()
- vIsOpen :: a -> IO Bool
- vIsClosed :: a -> IO Bool
- vTestOpen :: a -> IO ()
- vIsEOF :: a -> IO Bool
- vShow :: a -> IO String
- vMkIOError :: a -> IOErrorType -> String -> Maybe FilePath -> IOError
- vThrow :: a -> IOErrorType -> IO b
- vGetFP :: a -> IO (Maybe FilePath)
- vTestEOF :: a -> IO ()
- vGetChar :: a -> IO Char
- vGetLine :: a -> IO String
- vGetContents :: a -> IO String
- vReady :: a -> IO Bool
- vIsReadable :: a -> IO Bool
- vPutChar :: a -> Char -> IO ()
- vPutStr :: a -> String -> IO ()
- vPutStrLn :: a -> String -> IO ()
- vPrint :: Show b => a -> b -> IO ()
- vFlush :: a -> IO ()
- vIsWritable :: a -> IO Bool
- vSeek :: a -> SeekMode -> Integer -> IO ()
- vTell :: a -> IO Integer
- vRewind :: a -> IO ()
- vIsSeekable :: a -> IO Bool
- vSetBuffering :: a -> BufferMode -> IO ()
- vGetBuffering :: a -> IO BufferMode
- vPutBuf :: a -> Ptr b -> Int -> IO ()
- vGetBuf :: a -> Ptr b -> Int -> IO Int
- data StreamReader
- newStreamReader :: String -> IO StreamReader
- data MemoryBuffer
- newMemoryBuffer :: String -> (String -> IO ()) -> IO MemoryBuffer
- mbDefaultCloseFunc :: String -> IO ()
- getMemoryBuffer :: MemoryBuffer -> IO String
- data PipeReader
- data PipeWriter
- newHVIOPipe :: IO (PipeReader, PipeWriter)
Implementation Classes
class Show a => HVIO a where Source
This is the generic I/O support class. All objects that are to be used
in the HVIO system must provide an instance of HVIO
.
Functions in this class provide an interface with the same specification as the similar functions in System.IO. Please refer to that documentation for a more complete specification than is provided here.
Instances of HVIO
must provide vClose
, vIsEOF
, and either
vIsOpen
or vIsClosed
.
Implementators of readable objects must provide at least vGetChar
and vIsReadable
.
An implementation of vGetContents
is also highly suggested, since
the default cannot implement proper partial closing semantics.
Implementators of writable objects must provide at least vPutChar
and
vIsWritable
.
Implementators of seekable objects must provide at least
vIsSeekable
, vTell
, and vSeek
.
Close a file
vIsOpen :: a -> IO Bool Source
Test if a file is open
vIsClosed :: a -> IO Bool Source
Test if a file is closed
vTestOpen :: a -> IO () Source
Raise an error if the file is not open.
This is a new HVIO function and is implemented in terms of
vIsOpen
.
Whether or not we're at EOF. This may raise on exception on some items, most notably write-only Handles such as stdout. In general, this is most reliable on items opened for reading. vIsEOF implementations must implicitly call vTestOpen.
vShow :: a -> IO String Source
Detailed show output.
vMkIOError :: a -> IOErrorType -> String -> Maybe FilePath -> IOError Source
Make an IOError.
vThrow :: a -> IOErrorType -> IO b Source
Throw an IOError.
vGetFP :: a -> IO (Maybe FilePath) Source
Get the filename/object/whatever that this corresponds to. May be Nothing.
Throw an isEOFError if we're at EOF; returns nothing otherwise.
If an implementation overrides the default, make sure that it
calls vTestOpen at some point. The default implementation is
a wrapper around a call to vIsEOF
.
vGetChar :: a -> IO Char Source
Read one character
vGetLine :: a -> IO String Source
Read one line
vGetContents :: a -> IO String Source
Get the remaining contents. Please note that as a user of this
function, the same partial-closing semantics as are used in the
standard hGetContents
are encouraged from implementators,
but are not required. That means that, for instance,
a vGetChar
after a vGetContents
may return some undefined
result instead of the error you would normally get. You should
use caution to make sure your code doesn't fall into that trap,
or make sure to test your code with Handle or one of the
default instances defined in this module. Also, some implementations
may essentially provide a complete close after a call to vGetContents
.
The bottom line: after a call to vGetContents
, you should do nothing
else with the object save closing it with vClose
.
For implementators, you are highly encouraged to provide a correct implementation.
Indicate whether at least one item is ready for reading. This will always be True for a great many implementations.
vIsReadable :: a -> IO Bool Source
Indicate whether a particular item is available for reading.
vPutChar :: a -> Char -> IO () Source
Write one character
vPutStr :: a -> String -> IO () Source
Write a string
vPutStrLn :: a -> String -> IO () Source
Write a string with newline character after it
vPrint :: Show b => a -> b -> IO () Source
Write a string representation of the argument, plus a newline.
Flush any output buffers. Note: implementations should assure that a vFlush is automatically performed on file close, if necessary to ensure all data sent is written.
vIsWritable :: a -> IO Bool Source
Indicate whether or not this particular object supports writing.
vSeek :: a -> SeekMode -> Integer -> IO () Source
Seek to a specific location.
vTell :: a -> IO Integer Source
Get the current position.
Convenience function to reset the file pointer to the beginning
of the file. A call to vRewind h
is the
same as
.vSeek
h AbsoluteSeek 0
vIsSeekable :: a -> IO Bool Source
Indicate whether this instance supports seeking.
vSetBuffering :: a -> BufferMode -> IO () Source
Set buffering; the default action is a no-op.
vGetBuffering :: a -> IO BufferMode Source
Get buffering; the default action always returns NoBuffering.
vPutBuf :: a -> Ptr b -> Int -> IO () Source
Binary output: write the specified number of octets from the specified buffer location.
vGetBuf :: a -> Ptr b -> Int -> IO Int Source
Binary input: read the specified number of octets from the specified buffer location, continuing to read until it either consumes that much data or EOF is encountered. Returns the number of octets actually read. EOF errors are never raised; fewer bytes than requested are returned on EOF.
Standard HVIO Implementations
Handle
Handle is a member of HVIO
.
Stream Reader
data StreamReader Source
Simulate I/O based on a string buffer.
When a StreamReader
is created, it is initialized based on the contents of
a String
. Its contents are read lazily whenever a request is made to read
something from the StreamReader
. It
can be used, therefore, to implement filters (simply initialize it with the
result from, say, a map over hGetContents from another HVIO object), codecs,
and simple I/O testing. Because it is lazy, it need not hold the entire
string in memory. You can create a StreamReader
with a call to
newStreamReader
.
:: String | Initial contents of the |
-> IO StreamReader |
Create a new StreamReader
object.
Memory Buffer
data MemoryBuffer Source
A MemoryBuffer
simulates true I/O, but uses an in-memory buffer instead
of on-disk storage.
It provides
a full interface like Handle (it implements HVIOReader
, HVIOWriter
,
and HVIOSeeker
). However, it maintains an in-memory buffer with the
contents of the file, rather than an actual on-disk file. You can access
the entire contents of this buffer at any time. This can be quite useful
for testing I/O code, or for cases where existing APIs use I/O, but you
prefer a String representation. You can create a MemoryBuffer
with a call
to newMemoryBuffer
.
The present MemoryBuffer
implementation is rather inefficient, particularly
when reading towards the end of large files. It's best used for smallish
data storage. This problem will be fixed eventually.
:: String | Initial Contents |
-> (String -> IO ()) | close func |
-> IO MemoryBuffer |
Create a new MemoryBuffer
instance. The buffer is initialized
to the value passed, and the pointer is placed at the beginning of the file.
You can put things in it by using the normal vPutStr
calls, and reset to
the beginning by using the normal vRewind
call.
The function is called when vClose
is called, and is passed the contents of
the buffer at close time. You can use mbDefaultCloseFunc
if you don't want to
do anything.
To create an empty buffer, pass the initial value ""
.
mbDefaultCloseFunc :: String -> IO () Source
Default (no-op) memory buf close function.
getMemoryBuffer :: MemoryBuffer -> IO String Source
Grab the entire contents of the buffer as a string.
Unlike vGetContents
, this has no effect on the open status of the
item, the EOF status, or the current position of the file pointer.
Haskell Pipe
data PipeReader Source
The reading side of a Haskell pipe. Please see newHVIOPipe
for more
details.
data PipeWriter Source
The writing side of a Haskell pipe. Please see newHVIOPipe
for more
details.
newHVIOPipe :: IO (PipeReader, PipeWriter) Source
Create a Haskell pipe.
These pipes are analogous to the Unix
pipes that are available from System.Posix, but don't require Unix and work
only in Haskell. When you create a pipe, you actually get two HVIO objects:
a PipeReader
and a PipeWriter
. You must use the PipeWriter
in one
thread and the PipeReader
in another thread. Data that's written to the
PipeWriter
will then be available for reading with the PipeReader
. The
pipes are implemented completely with existing Haskell threading primitives,
and require no special operating system support. Unlike Unix pipes, these
pipes cannot be used across a fork(). Also unlike Unix pipes, these pipes
are portable and interact well with Haskell threads.