Bracket pattern
When acquiring, using, and releasing various resources, it can be quite convenient to write a function to manage the acquisition and releasing, taking a function of the acquired value that specifies an action to be performed in between.
The function bracket
from Control.Exception
can be used to build such functions in the IO monad. It is commonly partially applied to its first two parameters, giving a function which manages the resource in question, allocating it for the context of a specific action.
bracket :: IO a -- computation to run first ("acquire resource")
-> (a -> IO b) -- computation to run last ("release resource")
-> (a -> IO c) -- computation to run in-between
-> IO c
In general, throughout the libraries many functions having names beginning with with
are defined to manage various resources in this fashion. One passes in a function of the allocated resource that says what to do, and the with-function handles both the allocation and deallocation. For example, in System.IO
, there is a function:
withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
that manages opening and closing a filehandle, or in Foreign.Marshal.Array
, there is a function
withArray :: Storable a => [a] -> (Ptr a -> IO b) -> IO b
which manages the memory associated with a foreign array.
A common thing one might want to do is to collect up a list of such resource-managing with-functions and build from them a single with-function that manages the whole list of associated resources.
For this one can use what is effectively sequence
in the Cont
monad, "unwrapped" appropriately:
nest :: [(r -> a) -> a] -> ([r] -> a) -> a
nest xs = runCont (sequence (map Cont xs))
As an example of the use of nest
, here is a with-function that allocates each of a list of Strings
as a CString
(which as the name suggests, is a C-style pointer to an allocated block of memory holding the characters of the string), and then allocates a null-terminated array holding these CString
s, runs the given action, then deallocates all the resources.
withCStringArray0 :: [String] -> (Ptr CString -> IO a) -> IO a
withCStringArray0 strings act = nest (map withCString strings)
(\rs -> withArray0 nullPtr rs act)