Ramblings by Benjamin Kovach
Theme adapted from minimal by orderedlist.
Notes on managed-1.0.0: A monad for managed resources
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