snoyberg/conduit — pull request #56 by Palmik (2012-07-24 10:02:16+00:00)
Title: Registering action returning functions.

Hi,
this is not an actual issue but more of a feature request with a patch.

I found myself wanting to be able to `register` a function of the form `a -> IO ()` (with a default value of type `a` that would be used in case it was not `released`) and then depending on the context in which I call `release` supply a different value of type `a`.

To be more specific, in my case that function was `putMVar` -- of course one could possibly solve that case by registering `tryPutMVar default` instead or by not using `resourcet` at all and use `modifyMVar_` instead. But I believe such solutions would not always be feasible.

So I have decided to see what can be done ad come up with these additional functions:

```haskell
-- | Perform some allocation, and automatically register a cleanup action.
--
-- This is almost identical to calling the allocation and then
-- @registerF@ing the release action, but this properly handles masking of
-- asynchronous exceptions.
--
-- Since
allocateF :: IO a -- ^ allocate
-> (b -> a -> IO ()) -- ^ free resource
-> b -- ^ Default value of `b` to be used in case it's not `releasedF` or when using `release`.
-> m (ReleaseKeyF b, a)

-- | Register some funcion with its default argument that
-- will be called precisely once, either when 'runResourceT' is called,
-- or when the 'ReleaseKey' is passed to 'release'.
--
-- Since
registerF :: (a -> IO ()) -- ^ Function to be registered
-> a -- ^ Default value of `a` to be used in case it's not `releasedF` or when using `release`.
-> m (ReleaseKeyF a)

-- | Call a release function early with the given argument, and deregister
-- it from the list of cleanup actions to be performed.
--
-- Since
releaseF :: (ReleaseKeyF a) -> a -> m ()
```

And here are some examples of what you can do:

```haskell
registerF2 :: IO ()
registerF2 = R.runResourceT $ do
k1 <- R.registerF putStrLn "Default1"
k2 <- R.registerF putStrLn "Default2"
R.releaseF k1 "Hello World!"

data Foo =
A
| B
| C String
deriving Show

registerF3 :: IO ()
registerF3 = R.runResourceT $ do
k1 <- R.registerF print A
k2 <- R.registerF print B
R.releaseF k1 $ C "Hello World!"
```
This patch should be backwards compatible (`ReleaseKey` is not type synonym for `ReleaseKeyF ()`). I have also added few cases to `bench.hs` to see if it causes any noticeable slow down and on my system, when compiled with `-O2`, it did not.

There are few things that could be done in this patch but were not, mainly because I was not sure if they were beneficial (in case they are I have no problem implementing them).

* Implement `register'` in terms of `registerF'`
* Give default implementation to `register` and `allocate` (in terms of `registerF` and `allocateF`)
* Change the signature of `releaseF` to `releaseF :: (ReleaseKeyF a) -> Maybe a -> m ()` and then implement `release'` in terms of `releaseF'` and add a default implementation to `release`.

Please let me know if you see any issues with this patch. If there are, I would love to try and resolve them.