Announcing: the Resource monad

January 28, 2014

GravatarBy Michael Snoyman

Back in June, Gabriel Gonzalez wrote a blog post on the Resource monad. At the time, I thought it was an interesting idea, but I didn't have a very good use case for it. However, while working on Persistent 2.0, I realized it was a great way to abstract the concept of acquiring a database connection, and allow both ResourceT and non-ResourceT access to Persistent.

So with Gabriel's permission to steal his idea, I added the Resource monad to the resourcet package. The internal representation is slightly different than the one presented in Gabriel's blog post. In order to provide proper async exception safety, the internal structure is:

data Allocated a = Allocated !a !(IO ())

newtype Resource a = Resource ((forall b. IO b -> IO b) -> IO (Allocated a))

instance Monad Resource where
    return a = Resource (\_ -> return (Allocated a (return ())))
    Resource f >>= g' = Resource $ \restore -> do
        Allocated x free1 <- f restore
        let Resource g = g' x
        Allocated y free2 <- g restore `E.onException` free1
        return $! Allocated y (free2 `E.finally` free1)

Allocated provides a value and its cleanup method. Resource is a function from mask's restore function to an action returning Allocated. By being set up in this way, we know that async exceptions are not thrown in the intermediate steps of monadic bind.

Usage of the API is pretty simple. We can create a file-opening resource:

openFileResource :: FilePath -> IOMode -> Resource Handle
openFileResource fp mode = mkResource (openFile fp mode) hClose

Using the Applicative instance, we can then build this up into a Resource for allocating both a file reader and writer:

myHandles :: Resource (Handle, Handle)
myHandles = (,)
    <$> openFileResource "input.txt" ReadMode
    <*> openFileResource "output.txt" WriteMode

And then we can allocate these Handles with either the bracket pattern:

bracketCopy :: IO ()
bracketCopy = with myHandles $ \(input, output) ->
    sourceHandle input $$ sinkHandle output

or using ResourceT itself:

resourcetCopy :: IO ()
resourcetCopy = runResourceT $ do
    (releaseKey, (input, output)) <- allocateResource myHandles
    sourceHandle input $$ sinkHandle output
    release releaseKey

Hopefully others will find this abstraction useful as well.

Comments

comments powered by Disqus

Archives