simple-effects-0.12.0.0: src/Control/Effects/Async.hs
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-| The 'Async' effect allows you to fork new threads in monads other than just 'IO'.
-}
module Control.Effects.Async where
import Import
import Control.Effects
import qualified Control.Concurrent.Async as Async
import Control.Monad.Runnable
data Async m = AsyncMethods
{ _async :: forall a. m a -> m (AsyncThread m a)
, _waitAsync :: forall a. AsyncThread m a -> m a }
-- | The type that represents the forked computation in the monad @m@ that eventually computes
-- a value of type @a@. Depending on the monad, the computation may produce zero, one or even
-- multiple values of that type.
newtype AsyncThread m a = AsyncThread (Async.Async (m a))
deriving (Functor, Eq, Ord)
instance Effect Async where
type CanLift Async t = RunnableTrans t
mergeContext mm = AsyncMethods
(\a -> mm >>= ($ a) . _async)
(\a -> mm >>= ($ a) . _waitAsync)
liftThrough (AsyncMethods f g) = AsyncMethods
(\tma -> do
st <- currentTransState
!res <- lift (f (runTransformer tma st))
return $ mapAsync (lift >=> restoreTransState) res
)
(\a -> do
st <- currentTransState
res <- lift (g (mapAsync (`runTransformer` st) a))
restoreTransState res
)
where
mapAsync :: (m a -> n b) -> AsyncThread m a -> AsyncThread n b
mapAsync f' (AsyncThread as) = AsyncThread (fmap f' as)
-- | The 'IO' implementation uses the @async@ library.
instance MonadEffect Async IO where
effect = AsyncMethods
(fmap (AsyncThread . fmap return) . Async.async)
(\(AsyncThread as) -> join (Async.wait as))
-- | Fork a new thread to run the given computation. The monadic context is forked into the new
-- thread.
--
-- For example, if we use state, the current state value will be visible int he forked computation.
-- Depending on how we ultimately implement the state, modifying it may or may not be visible
-- from the main thread. If we use 'implementStateViaStateT' then setting the state in the forked
-- thread will just modify the thread-local value. On the other hand, if we use
-- 'implementStateViaIORef' then both the main thread and the new thread will use the same reference
-- meaning they can interact through it.
async :: MonadEffect Async m => m a -> m (AsyncThread m a)
-- | Wait for the thread to finish and return it's result. The monadic context will also be merged.
--
-- Example:
--
-- @
-- 'setState' \@Int 1
-- th <- 'async' $ do
-- 'setState' \@Int 2
-- 'waitAsync' th
-- print =<< 'getState' \@Int -- Outputs 2
-- @
waitAsync :: MonadEffect Async m => AsyncThread m a -> m a
AsyncMethods async waitAsync = effect
-- | This will discard the @'MonadEffect' 'Async' m@ constraint by forcing @m@ to be 'IO'.
-- The functions doesn't actually do anything, the real implementation is given by the
-- @'MonadEffect' 'Async' IO@ instance which uses the @async@ package.
implementAsyncViaIO :: IO a -> IO a
implementAsyncViaIO = id