Abstract Nonsense

Abstract Nonsense

Ramblings by Benjamin Kovach

Theme adapted from minimal by orderedlist.

Haskell for all: A monad for managed resources

Published by Ben Kovach on August 11, 2014

Tags: haskell, monad, managed

Notes on managed-1.0.0: A monad for managed resources

Package Link

Managed is originally from mvc.

Managed is this, which implements Functor, Applicative, Monad, and MonadIO:

newtype Managed a =
    Managed { with :: forall r . (a -> IO r) -> IO r }

Example (copy one file to another w/ pipes):

import Control.Monad.Managed
import System.IO
import Pipes
import qualified Pipes.Prelude as Pipes

main = runManaged $ do
    hIn  <- managed (withFile "in.txt" ReadMode)
    hOut <- managed (withFile "out.txt" WriteMode)
    liftIO $ runEffect $
        Pipes.fromHandle hIn >-> Pipes.toHandle hOut

This isn’t super great, but the Applicative is; it lifts operations from values that it wraps.

Applicatives can extend Monoids:

instance Monoid a => Monoid (Managed a)

but they can also extend Categories. Given any Category, extending it with an Applicative automatically defines a new one.

import Control.Applicative
import Control.Category
import Prelude hiding((.), id)

newtype Extend f c a b = Extend (f (c a b))

instance (Applicative f, Category c) 
  => Category (Extend f c) where
   id = Extend (pure id)
   Extend f . Extend g = Extend (liftA2 (.) f g) 

We can take advangage of this to extend one of the categories from pipes with simple resource management:

import Pipes

newtype Pull m a b = Pull (Pipe a b m ())

instance Monad m => Category (Pull m) where
  id = Pull cat
  Pull p . Pull q = Pull (p <-< q)

Now resource-managed pipes can be defined by Extending with the Managed Applicative, as above:

import Control.Monad.Managed
import qualified Pipes.Prelude as P
import System.IO

fromFile :: FilePath -> Extend Managed (Pull IO) () String
fromFile path = Extend $ do
  handle <- managed $ withFile path ReadMode
  return . Pull $ P.fromHandle handle

toFile :: FilePath -> Extend Managed (Pull IO) String X
toFile path = Extend $ do
  handle <- managed $ withFile path WriteMode
  return . Pull $ P.toHandle handle

Now we can just run an Extended pipe with runPipeline:

runPipeline :: Extend Managed (Pull IO) () X -> IO ()
runPipeline (Extend mp) = runManaged $ do
  Pull p <- mp
  liftIO . runEffect $ return () >~ p

Then we can compose Extended pipes:

main = runPipeline $ fromFile "in.txt" >>> toFile "out.txt"

It’s easy to generically reuse existing pipes as well:

reuse :: Monad m => Pipe a b m () -> Extend Managed (Pull m) a b
reuse = Extend . pure . Pull

main = runPipeline $ 
    fromFile "in.txt" >>> reuse (Pipes.take 2) >>> toFile "out.txt"

Laws for reuse:

reuse (p >-> q) = reuse p >>> reuse q
reuse cat = id

Notes: - Managed is a special case of Codensity or ContT. - Managed is closely related to Resource, which preserves open/close operations. However, Managed works for arbitrary callbacks.

For more on “connectable components,” (re)read this

Discussion on reddit

comments powered by Disqus