simple-effects-0.12.0.0: src/Control/Effects/Yield.hs
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-| The @'Yield' a@ effect lets a computation produce values of type @a@ during it's execution. -}
module Control.Effects.Yield where
import Import
import Control.Effects
import Control.Effects.Signal
import Control.Effects.List
import Control.Effects.Async
import GHC.Generics
import Control.Concurrent.MVar
import Control.Concurrent.Chan
newtype Yield a m = YieldMethods
{ _yield :: a -> m () }
deriving (Generic)
instance Effect (Yield a)
-- | Output a value of type @a@. The semantics are determined by the implementation, but usually this
-- will block until the next value is requested by the consumer.
yield :: forall a m. MonadEffect (Yield a) m => a -> m ()
YieldMethods yield = effect
-- | Implement 'Yield' by using non-determinism to output each of the values. This means you can
-- use the functions from "Control.Effects.List" to choose how to consume them. For example,
-- using 'evaluateToList' will give you a list of all yielded values. It also means the 'yield'
-- calls won't block since all the values are requested. Other consumer functions give you more
-- control.
implementYieldViaNonDeterminism ::
forall a m b. MonadEffect NonDeterminism m
=> RuntimeImplemented (Yield a) (RuntimeImplemented (Signal a ()) (ExceptT a m)) b
-> m a
implementYieldViaNonDeterminism m = m
& (>> deadEnd)
& implement (YieldMethods (signal @a))
& handleSignal @a (\a -> do
b <- choose [True, False]
return $ if b then Break a else Resume ())
-- | Implement 'Yield' through an 'MVar'. The result is a monadic action (the inner one) that
-- returns one yielded
-- value or 'Nothing' if the computation is finished. All subsequent calls will also return
-- 'Nothing'. Each execution of this action continues execution in the provided computation,
-- which is otherwise suspended.
--
-- If the provided computation forks new threads and doesn't wait for them to finish, 'Nothing'
-- may be returned prematurely (in the sense that maybe there's still a live thread yielding
-- values).
--
-- Since the yielding is done through a shared 'MVar', this
-- implementation is suitable to be run with multiple threads. Scheduling which thread gets
-- continued is defined by the semantics of 'MVar's.
--
-- [Note]
-- 'yield' will block in this implementation.
implementYieldViaMVar ::
forall a m b. (MonadIO m, MonadEffect Async m)
=> RuntimeImplemented (Yield a) m b -> m (m (Maybe a))
implementYieldViaMVar m = do
mv <- liftIO newEmptyMVar
block <- liftIO newEmptyMVar
done <- liftIO $ newMVar False
void $ async $ do
liftIO $ takeMVar block
void $ m & implement (YieldMethods (\a -> liftIO $ do
putMVar mv (Just a)
takeMVar block ))
liftIO $ do
void $ swapMVar done True
void $ tryPutMVar mv Nothing
return $ liftIO $ do
d <- readMVar done
if d then return Nothing
else do
putMVar block ()
takeMVar mv
-- | Implements 'Yield' through a 'Chan'. The resulting monadic action (the inner one) reads one
-- value from the queue. 'Nothing' means the provided computation is done. If the provided
-- computation forks new threads and doesn't wait for them to finish, 'Nothing' may be written
-- prematurely (in the sense that maybe there's still a live thread yielding values).
--
-- [Note]
-- 'yield' will /not/ block in this implementation.
implementYieldViaChan ::
forall a m b. (MonadIO m, MonadEffect Async m)
=> RuntimeImplemented (Yield a) m b -> m (m (Maybe a))
implementYieldViaChan m = do
ch <- liftIO newChan
done <- liftIO $ newMVar False
void $ async $ do
void $ m & implement (YieldMethods (liftIO . writeChan ch . Just))
liftIO $ writeChan ch Nothing
return $ liftIO $ do
d <- readMVar done
if d then return Nothing
else readChan ch >>= \case
Nothing -> do
void $ swapMVar done True
return Nothing
Just a -> return (Just a)
-- | A convenience function to go through all the yielded results. Use in combination with one
-- of the implementations. Collects a list of values.
traverseYielded :: Monad m => m (Maybe a) -> (a -> m b) -> m [b]
traverseYielded m f = m >>= \case
Nothing -> return []
Just a -> do
b <- f a
bs <- traverseYielded m f
return (b : bs)
-- | A convenience function to go through all the yielded results. Use in combination with one
-- of the implementations. Discards the computed values.
traverseYielded_ :: Monad m => m (Maybe a) -> (a -> m b) -> m ()
traverseYielded_ m f = m >>= \case
Nothing -> return ()
Just a -> f a >> traverseYielded_ m f