-- | @Bluefin.CloneableHandle@ defines the 'CloneableHandle' class, -- whose purpose is to support 'withEffToIOCloneHandle'. module Bluefin.CloneableHandle ( -- | 'withEffToIOCloneHandle' is an @IO@ unlifting function that -- clones its handle each time it runs @Eff@ in @IO@. This is -- convenient when the unlifting function is being used to fork -- threads, since Bluefin state is not threadsafe. Be careful -- when you use it, because it can be used to throw away the -- effect tag on a Bluefin @Eff@ action due to this part of -- its type (here throwing away @e@): -- -- @ -- (forall e. IOE e -> h e -> Eff e r) -> IO r -- @ -- -- It is only safely used when you do not allow any effects to -- escape their scope, so we suggest that you use it sparingly to -- define reusable combinators which themselves are safe. For -- example, here is how you could write an equivalent of @async@'s -- @race@ primitive: -- -- @ -- bluefinRace :: -- ('CloneableHandle' h, e1 :> es) => -- t'Bluefin.IO.IOE' e1 -> -- h es -> -- (forall e. IOE e -> h e -> t'Bluefin.Eff.Eff' e r) -> -- (forall e. IOE e -> h e -> Eff e r) -> -- Eff es r -- bluefinRace io h m1 m2 = withEffToIOCloneHandle io h $ \\runInIO -> do -- either id id -- \<$\> Control.Concurrent.Async.race -- (runInIO $ \\io' h' -> m1 io' h') -- (runInIO $ \\io' h' -> m2 io' h') -- @ -- -- Then you can safely use it to race Bluefin @Eff@ actions: -- -- @ -- example :: IO () -- example = 'Bluefin.Eff.runEff_' $ \\io -> 'Bluefin.State.evalState' 0 $ \\st -> do -- r \<- 'Bluefin.Exception.try' $ \\ex -> do -- bluefinRace -- io -- (MkMyHandle ('Bluefin.Handle.mapHandle' ex) (mapHandle st)) -- ( \\_ (MkMyHandle ex' st') -> do -- 'Bluefin.State.modify' st' (subtract 2000) -- 'Bluefin.Exception.throw' ex' "Aborting from branch 1" -- ) -- ( \\_ (MkMyHandle _ st') -> do -- modify st' (+ 3000) -- pure (2 :: Int) -- ) -- -- s <- 'Bluefin.State.get' st -- 'Bluefin.IO.effIO' io (print r) -- effIO io (putStrLn ("State started at 0 and was cloned. Now: " <> show s)) -- @ -- -- You can see from the output that the actions were raced as -- expected, and the @State@ was cloned so that changes to it in -- the branches of @race@ did not affect the original @State@. -- -- @ -- -- Run one time (the first thread was faster) -- ghci> example -- Right 2 -- State started at 0 and was cloned. Now: 0 -- -- Run another time (the second thread was faster) -- ghci> example -- Left "Aborting from branch 1" -- State started at 0 and was cloned. Now: 0 -- @ -- -- Note that @withEffToIOCloneHandle@ only allows access to /one/ -- external @Handle@ within it, so if you have several you'd like -- to use you'll have to define a new handle that bundles them -- together. withEffToIOCloneHandle, -- * @CloneableHandle@ CloneableHandle, GenericCloneableHandle(MkGenericCloneableHandle), GCloneableHandle, -- * @GHC.Generics@ re-exports Generic1, ) where import Bluefin.Internal.CloneableHandle