Imperative Object Destruction
Ashley Yakeley
ashley@semantic.org
Sun, 12 Nov 2000 23:20:31 -0800
C++ provides a convenient mechanism for cleaning up stuff, the
destructor, which is guaranteed to get called when an object passes out
of scope (or something).
I'm wondering how to make a Haskell equivalent for imperative code. For
instance, consider a simple file API, with these operations:
open: given a string (the filename), open an existing file with that
name, return a file-handle (or fail)
count: given a file-handle, get the length of the file
read: given a file-handle, offset into the file and a number of bytes,
read the file returning a list of bytes (or fail)
write: given a file-handle, offset into the file and a list of bytes,
write the bytes to the file at the offset
close: given a file-handle, close the handle
In C++ I could simply create an OpenFile class, with the close operation
in the destructor. The actual operations would be hidden from OpenFile's
clients.
I'd like to represent this API in Haskell so that the type-rules
guarantee that in any imperative action of type 'IO a',
1. every opened file gets closed exactly once
2. no read or write operations are performed on file-handles that have
not yet been opened, or that have already been closed.
My first guess was to create a function that encapsulated the entire
life-cycle of the file, passing in a function that would represent
whatever one wished to do on the file:
count :: Handle -> IO Integer
read :: (Integer,Integer) -> Handle -> IO [Byte]
write :: (Integer,[Byte]) -> Handle -> IO ()
withFile :: (Handle -> IO a) -> String -> IO a
'withFile operation name' would open the file called 'name', perform the
operation 'operation', and then close the file. So for instance, to get
the length of a file named "/home/ashley/foo":
withFile count "/home/ashley/foo"
Trouble is, one can easily pass a return function to withFile to get
ahold of the handle after it's been closed.
My second guess was to use a special type to represent an imperative
operation that needed a handle:
read :: (Integer,Integer) -> HandleOperation [Byte]
write :: (Integer,[Byte]) -> HandleOperation ()
withFile :: HandleOperation a -> String -> IO a
Of course, I'd then need to provide functions to compose/concatenate
HandleOperation values. But I can't help thinking this problem is already
well-known and there's a straightforward solution...
--
Ashley Yakeley, Seattle WA