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