packages feed

monad-schedule 0.1.2.2 → 0.2

raw patch · 12 files changed

+392/−53 lines, 12 filesdep +generic-arbitrarydep +operationaldep +timedep ~QuickCheckdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: generic-arbitrary, operational, time

Dependency ranges changed: QuickCheck, base

API changes (from Hackage documentation)

+ Control.Monad.Schedule.Class: apSchedule :: (MonadSchedule m, Monad m) => m (a -> b) -> m a -> m b
+ Control.Monad.Schedule.Class: scheduleWith :: (MonadSchedule m, Monad m) => m a -> m b -> m b
+ Control.Monad.Schedule.FreeAsync: ConcurrentlyT :: FreeAsyncT m a -> ConcurrentlyT m a
+ Control.Monad.Schedule.FreeAsync: FreeAsyncT :: ProgramT MVar m a -> FreeAsyncT m a
+ Control.Monad.Schedule.FreeAsync: [getConcurrentlyT] :: ConcurrentlyT m a -> FreeAsyncT m a
+ Control.Monad.Schedule.FreeAsync: [getFreeAsyncT] :: FreeAsyncT m a -> ProgramT MVar m a
+ Control.Monad.Schedule.FreeAsync: asyncMVar :: MVar a -> FreeAsyncT m a
+ Control.Monad.Schedule.FreeAsync: concurrently :: MonadIO m => IO a -> ConcurrentlyT m a
+ Control.Monad.Schedule.FreeAsync: concurrentlyMVar :: MVar a -> ConcurrentlyT m a
+ Control.Monad.Schedule.FreeAsync: freeAsync :: MonadIO m => IO a -> FreeAsyncT m a
+ Control.Monad.Schedule.FreeAsync: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Control.Monad.Schedule.FreeAsync.ConcurrentlyT m)
+ Control.Monad.Schedule.FreeAsync: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Control.Monad.Schedule.FreeAsync.FreeAsyncT m)
+ Control.Monad.Schedule.FreeAsync: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.Schedule.Class.MonadSchedule (Control.Monad.Schedule.FreeAsync.ConcurrentlyT m)
+ Control.Monad.Schedule.FreeAsync: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.Schedule.Class.MonadSchedule (Control.Monad.Schedule.FreeAsync.FreeAsyncT m)
+ Control.Monad.Schedule.FreeAsync: instance Control.Monad.IO.Class.MonadIO m => GHC.Base.Applicative (Control.Monad.Schedule.FreeAsync.ConcurrentlyT m)
+ Control.Monad.Schedule.FreeAsync: instance Control.Monad.IO.Class.MonadIO m => GHC.Base.Monad (Control.Monad.Schedule.FreeAsync.ConcurrentlyT m)
+ Control.Monad.Schedule.FreeAsync: instance Control.Monad.Trans.Class.MonadTrans Control.Monad.Schedule.FreeAsync.FreeAsyncT
+ Control.Monad.Schedule.FreeAsync: instance GHC.Base.Monad m => GHC.Base.Applicative (Control.Monad.Schedule.FreeAsync.FreeAsyncT m)
+ Control.Monad.Schedule.FreeAsync: instance GHC.Base.Monad m => GHC.Base.Functor (Control.Monad.Schedule.FreeAsync.ConcurrentlyT m)
+ Control.Monad.Schedule.FreeAsync: instance GHC.Base.Monad m => GHC.Base.Functor (Control.Monad.Schedule.FreeAsync.FreeAsyncT m)
+ Control.Monad.Schedule.FreeAsync: instance GHC.Base.Monad m => GHC.Base.Monad (Control.Monad.Schedule.FreeAsync.FreeAsyncT m)
+ Control.Monad.Schedule.FreeAsync: lift' :: Monad m => m a -> ConcurrentlyT m a
+ Control.Monad.Schedule.FreeAsync: newtype ConcurrentlyT m a
+ Control.Monad.Schedule.FreeAsync: newtype FreeAsyncT m a
+ Control.Monad.Schedule.FreeAsync: runConcurrently :: Concurrently a -> IO a
+ Control.Monad.Schedule.FreeAsync: runConcurrentlyT :: MonadIO m => ConcurrentlyT m a -> m a
+ Control.Monad.Schedule.FreeAsync: runFreeAsync :: FreeAsync a -> IO a
+ Control.Monad.Schedule.FreeAsync: runFreeAsyncT :: MonadIO m => FreeAsyncT m a -> m a
+ Control.Monad.Schedule.FreeAsync: type Concurrently = ConcurrentlyT IO
+ Control.Monad.Schedule.FreeAsync: type FreeAsync = FreeAsyncT IO

Files

CHANGELOG.md view
@@ -1,5 +1,11 @@ # Revision history for monad-schedule +## 0.2++* Added FreeAsync+* Dropped support below GHC 9.2+* Documentation fixes+ ## 0.1.2.2  * Compatibility with GHC 9.8
monad-schedule.cabal view
@@ -1,6 +1,6 @@ cabal-version:   2.4 name:            monad-schedule-version:         0.1.2.2+version:         0.2 license:         MIT license-file:    LICENSE author:          Manuel Bärenz@@ -18,9 +18,6 @@ extra-doc-files: CHANGELOG.md  tested-with:-  GHC == 8.8.4-  GHC == 8.10.7-  GHC == 9.0.2   GHC == 9.2.5   GHC == 9.4.5   GHC == 9.6.2@@ -32,16 +29,22 @@  common deps   build-depends:-    , base          >=4.13.0 && <4.20.0+    , base          >=4.16.0 && <4.20.0     , free          >=5.1 && < 5.3     , stm           ^>=2.5     , time-domain   ^>=0.1     , transformers  >=0.5 && < 0.7+    , operational   ^>=0.2.4+  if flag(dev)+    ghc-options: -Werror+  ghc-options:+    -W  library   import:           deps   exposed-modules:     Control.Monad.Schedule.Class+    Control.Monad.Schedule.FreeAsync     Control.Monad.Schedule.OSThreadPool     Control.Monad.Schedule.RoundRobin     Control.Monad.Schedule.Sequence@@ -56,6 +59,7 @@   type:             exitcode-stdio-1.0   main-is:          Main.hs   other-modules:+    FreeAsync     Trans     Yield @@ -63,9 +67,16 @@   build-depends:     , HUnit                       ^>=1.6     , monad-schedule-    , QuickCheck                  ^>=2.14+    , QuickCheck                  >=2.14 && <2.16     , test-framework              ^>=0.8     , test-framework-hunit        ^>=0.3     , test-framework-quickcheck2  ^>=0.3+    , generic-arbitrary           ^>=1.0+    , time                        >=1.9    default-language: Haskell2010++flag dev+  description: Enable warnings as errors. Active on ci.+  default: False+  manual: True
src/Control/Monad/Schedule/Class.hs view
@@ -13,24 +13,17 @@ -- base import Control.Arrow import Control.Concurrent-import Control.Monad (void)-import Control.Monad.IO.Class import Data.Either import Data.Foldable (fold, forM_) import Data.Function import Data.Functor.Identity-import Data.Kind (Type) import Data.List.NonEmpty hiding (length) import qualified Data.List.NonEmpty as NonEmpty-import Data.Maybe (fromJust)-import Data.Void-import Unsafe.Coerce (unsafeCoerce) import Prelude hiding (map, zip)  -- transformers import Control.Monad.Trans.Accum import Control.Monad.Trans.Class-import Control.Monad.Trans.Cont import Control.Monad.Trans.Except import Control.Monad.Trans.Identity import Control.Monad.Trans.Maybe@@ -49,10 +42,9 @@ Executing any of them is expected to be blocking, and awaits the return of the corresponding action. -A lawful instance is considered to satisfy these conditions:+A lawful instance is considered to preserve pure values.+Applying 'schedule' to values like @'pure' a@ will eventually return exactly these values. -  * The set of returned values is invariant under scheduling.-    In other words, @sequence@ will result in the same set of values as @scheduleAndFinish@. 'schedule' thus can be thought of as a concurrency-utilizing version of 'sequence'. -} class MonadSchedule m where@@ -93,8 +85,7 @@ instance MonadSchedule Identity where   schedule as = (,[]) <$> sequence as -{- |-Fork all actions concurrently in separate threads and wait for the first one to complete.+{- | Fork all actions concurrently in separate threads and wait for the first one to complete.  Many monadic actions complete at nondeterministic times (such as event listeners),@@ -102,6 +93,11 @@ with most other actions. Using concurrency, they can still be scheduled with all other actions in 'IO', by running them in separate GHC threads.++Caution: Using 'schedule' repeatedly on the returned continuations of a previous 'schedule' call+will add a layer of indirection to the continuation every time,+eventually slowing down performance and building up memory.+For a monad that doesn't have this problem, see 'Control.Monad.Schedule.FreeAsync.FreeAsyncT'. -} instance MonadSchedule IO where   schedule as = do@@ -260,3 +256,14 @@     Right (aCont, b) -> do       a <- aCont       return (a, b)++{- | Run both actions concurrently and apply the first result to the second.++Use as a concurrent replacement for '<*>' from 'Applicative'.+-}+apSchedule :: (MonadSchedule m, Monad m) => m (a -> b) -> m a -> m b+apSchedule f a = uncurry id <$> async f a++-- | Concurrent replacement for '*>' from 'Applicative' or '>>' from 'Monad'.+scheduleWith :: (MonadSchedule m, Monad m) => m a -> m b -> m b+scheduleWith a b = (id <$ a) `apSchedule` b
+ src/Control/Monad/Schedule/FreeAsync.hs view
@@ -0,0 +1,235 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE NamedFieldPuns #-}++-- | Asynchronicity implementation using 'MVar's and free monads.+module Control.Monad.Schedule.FreeAsync (+  -- * 'FreeAsyncT'+  FreeAsyncT (..),+  FreeAsync,+  freeAsync,+  asyncMVar,+  runFreeAsync,+  runFreeAsyncT,++  -- * Concurrent 'Applicative' interface+  ConcurrentlyT (..),+  Concurrently,+  concurrently,+  concurrentlyMVar,+  lift',+  runConcurrentlyT,+  runConcurrently,+)+where++-- base+import Control.Arrow (second, (>>>))+import Control.Concurrent (MVar, forkIO, newEmptyMVar, putMVar, takeMVar, threadDelay, tryTakeMVar, yield)+import Control.Monad.IO.Class+import Data.Either (partitionEithers)+import Data.List.NonEmpty (NonEmpty (..), appendList, toList)++-- transformers+import Control.Monad.Trans.Class++-- operational+import Control.Monad.Operational (ProgramT, ProgramViewT (..), interpretWithMonadT, singleton, unviewT, viewT)++-- monad-schedule+import Control.Monad.Schedule.Class (MonadSchedule (..), apSchedule)++{- | An 'IO'-like monad with the capability of async/await-style futures.++Synchronous (blocking) computations in this monad can be created using 'lift' and 'liftIO'.+Asynchronous computations that can run in the background are created with 'freeAsync' or 'asyncMVar'.++To leverage the asynchronicity, you can schedule computations with 'MonadSchedule' operations such as 'schedule' or 'race'.++Caution: Composing computations with 'Applicative' or 'Monad' operations like '<*>', '>>=' and @do@-notation+will force all but the final computation in order:+When running @a '*>' b '*>' c@, @b@ will not be started before @a@ has completed.+To start all operations and run them concurrently, use e.g. 'Control.Monad.Schedule.Class.scheduleWith'.+To use an 'Applicative' interface for concurrency, have a look at 'ConcurrentlyT'.+-}+newtype FreeAsyncT m a = FreeAsyncT {getFreeAsyncT :: ProgramT MVar m a}+  deriving newtype (Functor, Applicative, Monad, MonadTrans)++type FreeAsync = FreeAsyncT IO++-- FIXME MFunctor & MMonad instances pending https://github.com/HeinrichApfelmus/operational/pull/28/++-- | Lifts into 'FreeAsyncT' /without/ concurrency. See 'freeAsync'.+instance (MonadIO m) => MonadIO (FreeAsyncT m) where+  liftIO = lift . liftIO++{- | Run an 'IO' computation in the background.++This returns a "promise", or "future", which completes the computation when run.+-}+freeAsync :: (MonadIO m) => IO a -> FreeAsyncT m a+freeAsync action = FreeAsyncT $ do+  var <- liftIO newEmptyMVar+  liftIO $ forkIO $ putMVar var =<< action+  singleton var++-- | Complete all computations and remove the 'FreeAsyncT' layer.+runFreeAsyncT :: (MonadIO m) => FreeAsyncT m a -> m a+runFreeAsyncT = interpretWithMonadT (liftIO . takeMVar) . getFreeAsyncT++-- | Like 'runFreeAsyncT', but specialized to 'IO'.+runFreeAsync :: FreeAsync a -> IO a+runFreeAsync = runFreeAsyncT++{- | Asynchronously await an 'MVar'.++@'asyncMVar' var@ will attempt 'takeMVar' in a way that can be 'schedule'd concurrently with other 'asyncMVar's or 'freeAsync's.+-}+asyncMVar :: MVar a -> FreeAsyncT m a+asyncMVar = FreeAsyncT . singleton++data MVarCont m a = forall b.+  MVarCont+  { mvar :: MVar b+  , cont :: b -> ProgramT MVar m a+  }++embedMVarCont :: (Monad m) => MVarCont m a -> FreeAsyncT m a+embedMVarCont MVarCont {mvar, cont} = FreeAsyncT $ unviewT $ mvar :>>= cont++{- | Concurrently wait for the completion of 'IO' actions.+Has a slight runtime overhead over the direct @'MonadSchedule' 'IO'@ instance, but better fairness.+-}+instance (MonadIO m) => MonadSchedule (FreeAsyncT m) where+  schedule actions = retryForever $ getFreeAsyncT <$> actions+    where+      retryForever :: (MonadIO m) => NonEmpty (ProgramT MVar m a) -> FreeAsyncT m (NonEmpty a, [FreeAsyncT m a])+      retryForever actions = do+        -- Look at all actions+        views <- lift (mapM viewT actions)+        -- Have some of them finished?+        case partitionNonEmpty $ viewToEither <$> views of+          -- All have finished+          Left (as, []) -> return (as, [])+          -- Some have finished, some are waiting for MVars+          Left (as, cont : conts) -> do+            -- Peek at the MVars+            progressed <- lift $ tryProgresses $ cont :| conts+            -- Are some MVars present already?+            case progressed of+              -- Yes, some were present, step the corresponding actions+              Left (actions, conts) -> do+                -- Look at the progressed actions+                views <- lift (mapM viewT actions)+                -- Have some of them returned now?+                case partitionNonEmpty $ viewToEither <$> views of+                  -- Yes. Return those as well+                  Left (as', conts') -> return (as <> as', embedMVarCont <$> (conts ++ conts'))+                  -- No, they are blocked on other MVars now+                  Right conts' -> return (as, embedMVarCont <$> toList conts' <> conts)+              -- All MVars are still blocked+              Right conts -> return (as, embedMVarCont <$> toList conts)+          -- All actions are waiting for MVars+          Right conts -> do+            -- Retry until some MVars get unblocked+            (progressed, conts) <- lift $ retryProgresses conts+            -- Some MVars are unblocked, start over.+            retryForever $ appendList progressed $ getFreeAsyncT . embedMVarCont <$> conts++      viewToEither :: ProgramViewT MVar m a -> Either a (MVarCont m a)+      viewToEither (Return a) = Left a+      viewToEither (mvar :>>= cont) = Right MVarCont {mvar, cont}++      partitionNonEmpty :: NonEmpty (Either a b) -> Either (NonEmpty a, [b]) (NonEmpty b)+      partitionNonEmpty (Left a :| abs) = let (as, bs) = partitionEithers abs in Left (a :| as, bs)+      partitionNonEmpty (Right b :| abs) = case partitionEithers abs of+        ([], bs) -> Right $ b :| bs+        (a : as, bs) -> Left (a :| as, b : bs)++      tryProgress :: (MonadIO m) => MVarCont m a -> m (Either (ProgramT MVar m a) (MVarCont m a))+      tryProgress mvarcont@MVarCont {mvar, cont} = do+        result <- liftIO $ tryTakeMVar mvar+        return $ maybe (Right mvarcont) (Left . cont) result++      tryProgresses :: (MonadIO m) => NonEmpty (MVarCont m a) -> m (Either (NonEmpty (ProgramT MVar m a), [MVarCont m a]) (NonEmpty (MVarCont m a)))+      tryProgresses conts = do+        result <- partitionNonEmpty <$> mapM tryProgress conts+        case result of+          Left (progressed, []) -> return $ Left (progressed, [])+          Left (progressed, cont : conts) -> do+            inner <- tryProgresses $ cont :| conts+            case inner of+              Left (progressed', finalConts) -> return $ Left (progressed <> progressed', finalConts)+              Right finalConts -> return $ Left (progressed, toList finalConts)+          Right conts -> return $ Right conts++      retryProgresses :: (MonadIO m) => NonEmpty (MVarCont m a) -> m (NonEmpty (ProgramT MVar m a), [MVarCont m a])+      retryProgresses conts = do+        result <- tryProgresses conts+        case result of+          Left progress -> return progress+          Right _ -> do+            liftIO $ yield >> threadDelay 100+            retryProgresses conts++{- | Like 'FreeAsyncT', but leverages concurrency in the 'Applicative' interface.++The central difference to 'FreeAsyncT' is the 'Applicative' instance:+@concurrently a *> concurrently b *> concurrently c@ will launch all three actions immediately+and return when all actions have completed.+On the other hand, @concurrently a >>= f@ has to compute sequentially.++For more readable syntax, it can be useful to switch the @ApplicativeDo@ extension on.++The downside of this 'Applicative' instance is that 'ConcurrentlyT' can't be an instance of 'MonadTrans'.+As a drop-in replacement, the function 'lift'' is supplied.++Caution: To lift an 'IO' action concurrently, you need to use 'concurrently' and not 'liftIO'.+-}+newtype ConcurrentlyT m a = ConcurrentlyT {getConcurrentlyT :: FreeAsyncT m a}+  deriving newtype (Functor, Monad, MonadIO)++type Concurrently = ConcurrentlyT IO++{- | Lift an 'IO' action such that it can be run concurrently.++See 'freeAsync'.+-}+concurrently :: (MonadIO m) => IO a -> ConcurrentlyT m a+concurrently = ConcurrentlyT . freeAsync++-- | Like 'asyncMVar'+concurrentlyMVar :: MVar a -> ConcurrentlyT m a+concurrentlyMVar = ConcurrentlyT . asyncMVar++{- | Lift a computation to 'ConcurrentlyT'.++This replaces the missing 'MonadTrans' instance.++Caution: Computations lifted with this function cannot be scheduled concurrently!+If this is your intention, 'concurrently' needs to be used instead.+-}+lift' :: (Monad m) => m a -> ConcurrentlyT m a+lift' = ConcurrentlyT . lift++-- | Run a 'ConcurrentlyT' computation to completion, removing the newtype layers.+runConcurrentlyT :: (MonadIO m) => ConcurrentlyT m a -> m a+runConcurrentlyT = runFreeAsyncT . getConcurrentlyT++-- | Like 'runConcurrently', but specialised to 'IO'.+runConcurrently :: Concurrently a -> IO a+runConcurrently = runConcurrentlyT++instance (MonadIO m) => Applicative (ConcurrentlyT m) where+  pure = ConcurrentlyT . pure+  (<*>) = apSchedule++-- | Like 'FreeAsyncT', but executes actions composed via the 'Applicative' interface concurrently.+instance (MonadIO m) => MonadSchedule (ConcurrentlyT m) where+  schedule =+    fmap getConcurrentlyT+      >>> schedule+      >>> fmap (second (map ConcurrentlyT))+      >>> ConcurrentlyT
src/Control/Monad/Schedule/OSThreadPool.hs view
@@ -18,9 +18,8 @@  -- stm import Control.Concurrent.STM-import Control.Concurrent.STM.TChan --- rhine+-- monad-schedule import Control.Monad.Schedule.Class  newtype OSThreadPool (n :: Nat) a = OSThreadPool {unOSThreadPool :: IO a}
src/Control/Monad/Schedule/Sequence.hs view
@@ -7,7 +7,6 @@ import Control.Arrow ((>>>)) import Control.Monad.IO.Class import Data.Functor.Identity-import qualified Data.List.NonEmpty as NonEmpty  -- transformers import Control.Monad.Trans.Class
src/Control/Monad/Schedule/Trans.hs view
@@ -9,11 +9,7 @@ module Control.Monad.Schedule.Trans where  -- base-import Control.Arrow (Arrow (second))-import Control.Category ((>>>)) import Control.Concurrent-import qualified Control.Concurrent as C-import Control.Monad (join) import Data.Functor.Classes import Data.Functor.Identity import Data.List (partition)@@ -156,6 +152,7 @@       shiftListOnce actions = case partitionFreeF $ toList actions of         (a : as, waits) -> Left (a :| as, waits)         ([], Wait diff cont : waits) -> Right $ Wait diff (cont, shift diff <$> waits)+        ([], []) -> error "ScheduleT.shiftListOnce: Internal error. Please report as a bug: https://github.com/turion/monad-schedule/issues/new"        -- Repeatedly shift the list by the smallest available waiting duration       -- until one action returns as pure.
src/Control/Monad/Schedule/Yield.hs view
@@ -6,7 +6,6 @@ import Data.Functor.Identity (Identity (runIdentity))  -- monad-schedule-import Control.Monad.Schedule.Class import Control.Monad.Schedule.Trans  -- * 'YieldT'
+ test/FreeAsync.hs view
@@ -0,0 +1,106 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -fconstraint-solver-iterations=10 #-}++module FreeAsync where++-- base+import Control.Concurrent (newMVar, takeMVar)+import Control.Monad (replicateM_)+import Control.Monad.IO.Class+import Data.IORef (newIORef, readIORef, writeIORef)+import Data.List.NonEmpty (fromList)+import GHC.Conc (threadDelay)+import GHC.Generics (Generic)++-- time+import Data.Time (diffUTCTime, getCurrentTime)++-- test-framework+import Test.Framework (testGroup)++-- test-framework-hunit+import Test.Framework.Providers.HUnit (testCase)++-- test-framework-QuickCheck2+import Test.Framework.Providers.QuickCheck2 (testProperty)++-- QuickCheck2+import Test.QuickCheck (Arbitrary, genericShrink, ioProperty, (.&&.), (===))+import Test.QuickCheck.Arbitrary.Generic (Arbitrary (..), genericArbitrary)++-- hunit+import Test.HUnit ((@?=))+import Test.HUnit.Base (assert)++-- monad-schedule+import Control.Monad.Schedule.Class (async, scheduleAndFinish)+import Control.Monad.Schedule.FreeAsync (FreeAsync, asyncMVar, concurrently, freeAsync, runConcurrently, runFreeAsync)++tests =+  testGroup+    "FreeAsync"+    [ testCase "Can lift and run an IORef action" $ do+        ref <- newIORef ""+        runFreeAsync $ liftIO $ writeIORef ref "Hello"+        result <- readIORef ref+        result @?= "Hello"+    , testProperty "Can schedule two values" $ \(v1 :: Value) (v2 :: Value) -> ioProperty $ do+        (result1, result2) <- runFreeAsync $ async (interpretValue 23 v1) (interpretValue 42 v2)+        return $ result1 === 23 .&&. result2 === 42+    , testCase "Can schedule two MVars" $ do+        var1 <- newMVar 23+        var2 <- newMVar 42+        result <- runFreeAsync $ async (asyncMVar var1) (asyncMVar var2)+        result @?= (23, 42)+    , testCase "Can schedule lifted retrieval of two MVars" $ do+        var1 <- newMVar 23+        var2 <- newMVar 42+        result <- runFreeAsync $ async (liftIO $ takeMVar var1) (liftIO $ takeMVar var2)+        result @?= (23, 42)+    , testCase "Sequencing adds times" $ do+        before <- getCurrentTime+        runFreeAsync $ replicateM_ 100 $ freeAsync $ threadDelay 1000+        after <- getCurrentTime+        let diff = after `diffUTCTime` before+        assert $ diff >= 0.1 && diff < 0.2+    , testCase "Scheduling does things in parallel" $ do+        before <- getCurrentTime+        runFreeAsync $ scheduleAndFinish $ fromList $ replicate 100 $ freeAsync $ threadDelay 1000+        after <- getCurrentTime+        let diff = after `diffUTCTime` before+        print diff+        assert $ diff > 0.001 && diff <= 0.005 -- Assume overhead is at most 5x at ms durations+    , testGroup+        "ConcurrentlyT"+        [ testCase "Sequencing does things in parallel" $ do+            before <- getCurrentTime+            runConcurrently $ replicateM_ 100 $ concurrently $ threadDelay 1000+            after <- getCurrentTime+            let diff = after `diffUTCTime` before+            print diff+            assert $ diff > 0.001 && diff <= 0.005 -- Assume overhead is at most 5x at ms durations+        ]+    ]++data Value+  = Pure+  | Lifted+  | FreeAsync_+  | AsyncMVar+  | TakeMVar+  | TakeMVarSingleLift+  deriving (Show, Generic)++instance Arbitrary Value where+  arbitrary = genericArbitrary+  shrink = genericShrink++interpretValue :: a -> Value -> FreeAsync a+interpretValue a Pure = pure a+interpretValue a Lifted = liftIO (pure a)+interpretValue a FreeAsync_ = freeAsync $ pure a+interpretValue a AsyncMVar = liftIO (newMVar a) >>= asyncMVar+interpretValue a TakeMVar = liftIO (newMVar a) >>= liftIO . takeMVar+interpretValue a TakeMVarSingleLift = liftIO (newMVar a >>= takeMVar)
test/Main.hs view
@@ -1,27 +1,10 @@ {-# LANGUAGE FlexibleInstances #-} --- base-import Control.Arrow-import Control.Monad-import Data.Functor.Identity-import Data.List.NonEmpty- -- test-framework import Test.Framework --- test-framework-hunit-import Test.Framework.Providers.HUnit---- HUnit-import Test.HUnit hiding (Test)---- test-framework-quickcheck2-import Test.Framework.Providers.QuickCheck2---- QuickCheck-import Test.QuickCheck- -- monad-schedule (test)+import qualified FreeAsync import qualified Trans import qualified Yield @@ -30,6 +13,7 @@  tests :: [Test] tests =-  [ Trans.tests+  [ FreeAsync.tests+  , Trans.tests   , Yield.tests   ]
test/Trans.hs view
@@ -7,21 +7,17 @@  -- base import Control.Arrow-import Control.Monad (forever, void)+import Control.Monad (forever) import Data.List (sort) import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NonEmpty  -- transformers import Control.Monad.Trans.Class-import Control.Monad.Trans.Writer (Writer, execWriter, runWriter, tell)---- free-import Control.Monad.Free (_Free)+import Control.Monad.Trans.Writer (Writer, execWriter, tell)  -- QuickCheck import Test.QuickCheck-import qualified Test.QuickCheck as QuickCheck  -- test-framework import Test.Framework
test/Yield.hs view
@@ -17,7 +17,7 @@ import Control.Monad.Trans.Writer (Writer, execWriter, runWriter, tell)  -- QuickCheck-import Test.QuickCheck (NonEmptyList (NonEmpty), counterexample, (===), (==>))+import Test.QuickCheck (counterexample, (==>))  -- test-framework import Test.Framework@@ -33,7 +33,7 @@  -- monad-schedule import Control.Monad.Schedule.Class (schedule, scheduleAndFinish)-import Control.Monad.Schedule.Trans (runScheduleIO, runScheduleT)+import Control.Monad.Schedule.Trans (runScheduleT) import Control.Monad.Schedule.Yield  sampleActions :: NonEmpty (MySchedule ())