[Haskell-cafe] Bracket around every IO computation monad
Bas van Dijk
v.dijk.bas at gmail.com
Mon Nov 15 14:31:51 EST 2010
On Mon, Nov 8, 2010 at 1:40 AM, Mitar <mmitar at gmail.com> wrote:
> Hi!
>
> I have a class Neuron which has (among others) two functions: attach
> and deattach. I would like to make a way to call a list/stack/bunch of
> attach functions in a way that if any of those fail (by exception),
> deattach for previously already attached values (called attach on
> them) are deattached (called deattach on them).
>
> I have come up with such way:
>
> data Growable where
> Growable :: Neuron n => LiveNeuron n -> Growable
>
> growNeurons :: [IO Growable] -> IO [Growable]
> growNeurons attaches = growNeurons' attaches []
> where growNeurons' [] ls = return ls
> growNeurons' (a:ats) ls = bracketOnError a (\(Growable l) ->
> deattach l) (\l -> growNeurons' ats (l:ls))
>
> So I give growNeurons a list of attach actions and it returns a list
> of attached values ((live)neurons). This works nice, but syntax to use
> it is ugly:
>
> neurons <- growNeurons [
> do { a <- attach nerve1; return $ Growable a },
> do { a <- attach nerve2; return $ Growable a },
> do { a <- attach nerve3; return $ Growable a }
> ]
>
> Types of attach and deattach are (if I simplify):
>
> attach :: Nerve n -> IO (LiveNeuron n)
> deattach :: LiveNeuron n -> IO ()
>
> Growable is only used so that I can put actions of different type in the list.
>
> It seems to me that all this could be wrapped into a monad. So that I
> would be able to call something like:
>
> neurons <- growNeurons' $ do
> attach nerve1
> attach nerve2
> attach nerve3
>
> Where I would be allowed to call actions of a special type class which
> defined also clean-up function (in my case called deattach). And which
> would be called if there was any exception thrown (and then at the end
> rethrown). Otherwise, the result would be a list of all computed
> values. In my case all this in IO monad.
>
> So it is possible that evaluation of monad actions would be stacked
> inside of bracketOnError and in a case of error clean-up functions
> would be called, otherwise returns a list of results?
>
>
> Mitar
This can be solved by using "Lightweight Monadic Regions" as
implemented in my regions package[1]. What follows is an example
module that demonstrates the idea:
module SaveNeurons (Nerve, SaveNeuron, saveAttach, someSaveOperation) where
----------------------------------------------------------------------
-- Imports:
-- from base:
import Control.Monad ( liftM )
-- from monad-peel:
import Control.Exception.Peel ( block )
import Control.Monad.IO.Peel ( MonadPeelIO )
-- from transformers:
import Control.Monad.IO.Class ( MonadIO(liftIO) )
-- from regions:
import Control.Monad.Trans.Region ( RegionT, runRegionT
, Dup(dup)
, AncestorRegion
)
import Control.Monad.Trans.Region.OnExit ( FinalizerHandle, onExit )
----------------------------------------------------------------------
-- Your existing types and functions:
data Nerve n = Nerve
data LiveNeuron n = LiveNeuron
attach :: Nerve n -> IO (LiveNeuron n)
attach = undefined
deattach :: LiveNeuron n -> IO ()
deattach = undefined
-- You probably also defined some operations on LiveNeurons:
someOperation :: LiveNeuron n -> IO ()
someOperation = undefined
----------------------------------------------------------------------
-- Save regional layer:
data SaveNeuron n r = SaveNeuron (LiveNeuron n) (FinalizerHandle r)
saveAttach :: MonadPeelIO pr
=> Nerve n
-> RegionT s pr (SaveNeuron n (RegionT s pr))
saveAttach nerve = block $ do
n <- liftIO $ attach nerve
fh <- onExit $ deattach n
return $ SaveNeuron n fh
someSaveOperation :: (AncestorRegion pr cr, MonadIO cr)
=> SaveNeuron n pr -> cr ()
someSaveOperation (SaveNeuron ln _) = liftIO $ someOperation ln
instance Dup (SaveNeuron n) where
dup (SaveNeuron ln fh) = liftM (SaveNeuron ln) $ dup fh
----------------------------------------------------------------------
-- Example:
example :: IO ()
example = runRegionT $ do
sn1 <- saveAttach Nerve
someSaveOperation sn1
-- When one of these operations fail,
-- all attached nerves will be detached automatically.
sn2 <- saveAttach Nerve
sn3 <- saveAttach Nerve
someSaveOperation sn2
someSaveOperation sn3
-- We can also nest regions.
runRegionT $ do
someSaveOperation sn1
sn4 <- saveAttach Nerve
sn5 <- saveAttach Nerve
someSaveOperation sn4
someSaveOperation sn5
-- When a regions terminates all attached nerves will
-- be detached automatically. Note that is a type error
-- to return a save neuron from a regions:
-- 'return sn5' gives a type error for example.
-- If you really wish to return a save neuron you can
-- 'duplicate' it to the parent region as in:
-- sn5' <- dup sn5
-- return sn5'
----------------------------------------------------------------------
Feel free to ask any questions about the above code.
Regards,
Bas
[1] http://hackage.haskell.org/package/regions
More information about the Haskell-Cafe
mailing list