Announcing: the Resource monad
January 28, 2014
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) hCloseUsing 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" WriteModeAnd then we can allocate these Handles with either the bracket pattern:
bracketCopy :: IO ()
bracketCopy = with myHandles $ \(input, output) ->
sourceHandle input $$ sinkHandle outputor using ResourceT itself:
resourcetCopy :: IO ()
resourcetCopy = runResourceT $ do
(releaseKey, (input, output)) <- allocateResource myHandles
sourceHandle input $$ sinkHandle output
release releaseKeyHopefully others will find this abstraction useful as well.