box 0.8.1 → 0.9.0
raw patch · 9 files changed
+451/−172 lines, 9 filesdep +asyncdep +bytestringdep +stmdep −concurrencydep ~textPVP ok
version bump matches the API change (PVP)
Dependencies added: async, bytestring, stm
Dependencies removed: concurrency
Dependency ranges changed: text
API changes (from Hackage documentation)
- Box.IO: fileAppendC :: FilePath -> CoCommitter IO Text
- Box.IO: fileWriteC :: FilePath -> CoCommitter IO Text
- Box.Time: Stamped :: !UTCTime -> !a -> Stamped a
- Box.Time: [stamp] :: Stamped a -> !UTCTime
- Box.Time: [value] :: Stamped a -> !a
- Box.Time: data Stamped a
- Box.Time: emitIn :: Emitter IO (Double, a) -> Emitter IO a
- Box.Time: instance GHC.Classes.Eq a => GHC.Classes.Eq (Box.Time.Stamped a)
- Box.Time: instance GHC.Read.Read a => GHC.Read.Read (Box.Time.Stamped a)
- Box.Time: instance GHC.Show.Show a => GHC.Show.Show (Box.Time.Stamped a)
+ Box.Box: glueES :: Monad m => s -> Committer m a -> Emitter (StateT s m) a -> m ()
+ Box.Box: glueS :: Monad m => s -> Committer (StateT s m) a -> Emitter (StateT s m) a -> m ()
+ Box.Codensity: infixr 0 <$|>
+ Box.Connectors: evalEmitter :: s -> Emitter (StateT s IO) a -> CoEmitter IO a
+ Box.Connectors: evalEmitterWith :: Queue a -> s -> Emitter (StateT s IO) a -> CoEmitter IO a
+ Box.Connectors: qListWith :: Queue a -> [a] -> CoEmitter IO a
+ Box.Connectors: sinkWith :: Queue a -> Int -> (a -> IO ()) -> CoCommitter IO a
+ Box.Connectors: sourceWith :: Queue a -> Int -> IO a -> CoEmitter IO a
+ Box.Connectors: takeQ :: Queue a -> Int -> Emitter IO a -> CoEmitter IO a
+ Box.Emitter: dropE :: Monad m => Int -> Emitter m a -> CoEmitter m a
+ Box.IO: changer :: Eq a => a -> Emitter IO a -> CoEmitter IO Bool
+ Box.IO: fileC :: FilePath -> IOMode -> BufferMode -> (Handle -> Committer IO a) -> CoCommitter IO a
+ Box.IO: fileCBS :: FilePath -> BufferMode -> IOMode -> CoCommitter IO ByteString
+ Box.IO: fileCText :: FilePath -> BufferMode -> IOMode -> CoCommitter IO Text
+ Box.IO: fileEBS :: FilePath -> BufferMode -> CoEmitter IO ByteString
+ Box.IO: fileEText :: FilePath -> BufferMode -> CoEmitter IO Text
+ Box.IO: logConsoleC :: Show a => String -> Committer IO a -> Committer IO a
+ Box.IO: logConsoleE :: Show a => String -> Emitter IO a -> Emitter IO a
+ Box.IO: pauser :: Emitter IO Bool -> Emitter IO a -> Emitter IO a
+ Box.IO: quit :: Emitter IO Bool -> IO a -> IO (Either Bool a)
+ Box.IO: restart :: Emitter IO Bool -> IO a -> IO (Either Bool a)
+ Box.Queue: fromActionWith :: Queue a -> Queue b -> (Box IO a b -> IO r) -> CoBox IO b a
+ Box.Queue: toBoxM :: Queue a -> IO (Box IO a a, IO ())
+ Box.Queue: toBoxSTM :: Queue a -> STM (Box STM a a, STM ())
+ Box.Time: fromGaps :: LocalTime -> Emitter IO (Gap, a) -> CoEmitter IO (LocalTime, a)
+ Box.Time: fromGapsNow :: Emitter IO (Gap, a) -> CoEmitter IO (LocalTime, a)
+ Box.Time: gapEffect :: Emitter IO (Gap, a) -> Emitter IO a
+ Box.Time: gapSkipEffect :: Emitter IO Int -> Emitter IO Gap -> CoEmitter IO Gap
+ Box.Time: gaps :: Emitter IO (LocalTime, a) -> CoEmitter IO (Gap, a)
+ Box.Time: skip :: Int -> Emitter IO (Gap, a) -> CoEmitter IO (Gap, a)
+ Box.Time: speedEffect :: Emitter IO Gap -> Emitter IO (Gap, a) -> Emitter IO a
+ Box.Time: speedSkipEffect :: Emitter IO (Int, Gap) -> Emitter IO (Gap, a) -> CoEmitter IO a
+ Box.Time: type Gap = Double
- Box.Connectors: bufferCommitter :: MonadConc m => Committer m a -> CoCommitter m a
+ Box.Connectors: bufferCommitter :: Committer IO a -> CoCommitter IO a
- Box.Connectors: bufferEmitter :: MonadConc m => Emitter m a -> CoEmitter m a
+ Box.Connectors: bufferEmitter :: Emitter IO a -> CoEmitter IO a
- Box.Connectors: concurrentC :: MonadConc m => Queue a -> Committer m a -> Committer m a -> CoCommitter m a
+ Box.Connectors: concurrentC :: Queue a -> Committer IO a -> Committer IO a -> CoCommitter IO a
- Box.Connectors: concurrentE :: MonadConc f => Queue a -> Emitter f a -> Emitter f a -> CoEmitter f a
+ Box.Connectors: concurrentE :: Queue a -> Emitter IO a -> Emitter IO a -> CoEmitter IO a
- Box.Connectors: qList :: MonadConc m => [a] -> CoEmitter m a
+ Box.Connectors: qList :: [a] -> CoEmitter IO a
- Box.Connectors: sink :: MonadConc m => Int -> (a -> m ()) -> CoCommitter m a
+ Box.Connectors: sink :: Int -> (a -> IO ()) -> CoCommitter IO a
- Box.Connectors: source :: MonadConc m => Int -> m a -> CoEmitter m a
+ Box.Connectors: source :: Int -> IO a -> CoEmitter IO a
- Box.IO: fileE :: FilePath -> CoEmitter IO Text
+ Box.IO: fileE :: FilePath -> BufferMode -> IOMode -> (Handle -> Emitter IO a) -> CoEmitter IO a
- Box.IO: handleC :: Handle -> Committer IO Text
+ Box.IO: handleC :: (Handle -> a -> IO ()) -> Handle -> Committer IO a
- Box.IO: handleE :: Handle -> Emitter IO Text
+ Box.IO: handleE :: (IsString a, Eq a) => (Handle -> IO a) -> Handle -> Emitter IO a
- Box.IO: refCommitter :: MonadConc m => m (Committer m a, m [a])
+ Box.IO: refCommitter :: IO (Committer IO a, IO [a])
- Box.IO: refEmitter :: MonadConc m => [a] -> m (Emitter m a)
+ Box.IO: refEmitter :: [a] -> IO (Emitter IO a)
- Box.Queue: commitQ :: MonadConc m => Queue a -> (Emitter m a -> m r) -> CoCommitter m a
+ Box.Queue: commitQ :: Queue a -> (Emitter IO a -> IO r) -> CoCommitter IO a
- Box.Queue: emitQ :: MonadConc m => Queue a -> (Committer m a -> m r) -> CoEmitter m a
+ Box.Queue: emitQ :: Queue a -> (Committer IO a -> IO r) -> CoEmitter IO a
- Box.Queue: fromAction :: MonadConc m => (Box m a b -> m r) -> CoBox m b a
+ Box.Queue: fromAction :: (Box IO a b -> IO r) -> CoBox IO b a
- Box.Queue: queue :: MonadConc m => Queue a -> (Committer m a -> m l) -> (Emitter m a -> m r) -> m (l, r)
+ Box.Queue: queue :: Queue a -> (Committer IO a -> IO l) -> (Emitter IO a -> IO r) -> IO (l, r)
- Box.Queue: queueL :: MonadConc m => Queue a -> (Committer m a -> m l) -> (Emitter m a -> m r) -> m l
+ Box.Queue: queueL :: Queue a -> (Committer IO a -> IO l) -> (Emitter IO a -> IO r) -> IO l
- Box.Queue: queueR :: MonadConc m => Queue a -> (Committer m a -> m l) -> (Emitter m a -> m r) -> m r
+ Box.Queue: queueR :: Queue a -> (Committer IO a -> IO l) -> (Emitter IO a -> IO r) -> IO r
- Box.Time: replay :: Double -> Emitter IO (LocalTime, a) -> CoEmitter IO a
+ Box.Time: replay :: Double -> Int -> Emitter IO (LocalTime, a) -> CoEmitter IO a
- Box.Time: sleep :: MonadConc m => Double -> m ()
+ Box.Time: sleep :: Double -> IO ()
- Box.Time: stampE :: (MonadConc m, MonadIO m) => Emitter m a -> Emitter m (LocalTime, a)
+ Box.Time: stampE :: Emitter IO a -> Emitter IO (LocalTime, a)
- Box.Time: stampNow :: (MonadConc m, MonadIO m) => a -> m (LocalTime, a)
+ Box.Time: stampNow :: a -> IO (LocalTime, a)
Files
- box.cabal +6/−4
- src/Box/Box.hs +19/−0
- src/Box/Codensity.hs +1/−1
- src/Box/Committer.hs +1/−2
- src/Box/Connectors.hs +71/−27
- src/Box/Emitter.hs +12/−0
- src/Box/IO.hs +141/−29
- src/Box/Queue.hs +67/−63
- src/Box/Time.hs +133/−46
box.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: box-version: 0.8.1+version: 0.9.0 synopsis: boxes description: A profunctor effect category: project@@ -12,7 +12,7 @@ license: BSD-3-Clause license-file: LICENSE build-type: Simple-tested-with: GHC ==8.8.4 || ==8.10.7 || ==9.2.1+tested-with: GHC ==8.8.4 || ==8.10.7 || ==9.2.2 extra-source-files: ChangeLog.md source-repository head@@ -41,8 +41,9 @@ -Wunused-packages build-depends:+ , async ^>= 2.2 , base >=4.7 && <5- , concurrency ^>=1.11+ , bytestring ^>=0.11 , containers ^>=0.6 , contravariant ^>=1.5 , dlist ^>=1.0@@ -51,7 +52,8 @@ , mtl ^>=2.2.2 , profunctors ^>=5.6 , semigroupoids ^>=5.3- , text ^>=1.2+ , stm ^>= 2.5.1+ , text >=1.2 && < 2.1 , time ^>=1.9 , transformers ^>=0.5
src/Box/Box.hs view
@@ -17,6 +17,8 @@ foistb, glue, glueN,+ glueES,+ glueS, fuse, Divap (..), DecAlt (..),@@ -52,6 +54,7 @@ -- >>> import Box -- >>> import Data.Text (pack) -- >>> import Data.Bool+-- >>> import Control.Monad.State.Lazy -- | A Box is a product of a 'Committer' and an 'Emitter'. --@@ -89,6 +92,22 @@ -- 3 glue :: (Monad m) => Committer m a -> Emitter m a -> m () glue c e = fix $ \rec -> emit e >>= maybe (pure False) (commit c) >>= bool (pure ()) rec++-- | Connect a Stateful emitter to a (non-stateful) committer of the same type, supplying initial state.+--+-- >>> glueES 0 (showStdout) <$|> (takeE 2 <$> qList [1..3])+-- 1+-- 2+glueES :: (Monad m) => s -> Committer m a -> Emitter (StateT s m) a -> m ()+glueES s c e = flip evalStateT s $ glue (foist lift c) e++-- | Connect a Stateful emitter to a (similarly-stateful) committer of the same type, supplying initial state.+--+-- >>> glueS 0 (foist lift showStdout) <$|> (takeE 2 <$> qList [1..3])+-- 1+-- 2+glueS :: (Monad m) => s -> Committer (StateT s m) a -> Emitter (StateT s m) a -> m ()+glueS s c e = flip evalStateT s $ glue c e -- | Glues a committer and emitter, and takes n emits --
src/Box/Codensity.hs view
@@ -51,7 +51,7 @@ process :: forall a m r. (a -> m r) -> Codensity m a -> m r process f k = runCodensity k f -infixr 3 <$|>+infixr 0 <$|> -- | fmap then close over a Codensity --
src/Box/Committer.hs view
@@ -103,8 +103,7 @@ -- 2 -- 3 listC :: (Monad m) => Committer m a -> Committer m [a]-listC c = Committer $ \as ->- or <$> sequence (commit c <$> as)+listC c = Committer $ fmap or . mapM (commit c) -- | Push to a state sequence. --
src/Box/Connectors.hs view
@@ -10,16 +10,22 @@ -- | Various ways to connect things up. module Box.Connectors ( qList,+ qListWith, popList, pushList, pushListN, sink,+ sinkWith, source,+ sourceWith, forkEmit, bufferCommitter, bufferEmitter, concurrentE, concurrentC,+ takeQ,+ evalEmitter,+ evalEmitterWith, ) where @@ -29,8 +35,7 @@ import Box.Emitter import Box.Functor import Box.Queue-import Control.Concurrent.Classy.Async as C-import Control.Monad.Conc.Class (MonadConc)+import Control.Concurrent.Async import Control.Monad.State.Lazy import Data.Foldable import qualified Data.Sequence as Seq@@ -43,13 +48,20 @@ -- >>> import Data.Bool -- >>> import Control.Monad --- | Queue a list.+-- | Queue a list Unbounded. -- -- >>> pushList <$|> qList [1,2,3] -- [1,2,3]-qList :: (MonadConc m) => [a] -> CoEmitter m a-qList xs = emitQ Unbounded (\c -> fmap and (traverse (commit c) xs))+qList :: [a] -> CoEmitter IO a+qList xs = qListWith Unbounded xs +-- | Queue a list with an explicit 'Queue'.+--+-- >>> pushList <$|> qListWith Single [1,2,3]+-- [1,2,3]+qListWith :: Queue a -> [a] -> CoEmitter IO a+qListWith q xs = emitQ q (\c -> fmap and (traverse (commit c) xs))+ -- | Directly supply a list to a committer action, via pop. -- -- >>> popList [1..3] showStdout@@ -57,7 +69,7 @@ -- 2 -- 3 popList :: Monad m => [a] -> Committer m a -> m ()-popList xs c = flip evalStateT (Seq.fromList xs) $ glue (foist lift c) pop+popList xs c = glueES (Seq.fromList xs) c pop -- | Push an Emitter into a list, via push. --@@ -81,28 +93,42 @@ Nothing -> pure () Just a' -> f a' --- | Create a finite Committer.+-- FIXME: This doctest sometimes fails with the last value not being printed. Hypothesis: the pipe collapses before the console print effect happens.++-- | Create a finite Committer Unbounded Queue. ----- >>> glue <$> sink 2 print <*|> qList [1..3]+-- > glue <$> sink 2 print <*|> qList [1..3] -- 1 -- 2-sink :: (MonadConc m) => Int -> (a -> m ()) -> CoCommitter m a-sink n f = commitQ Unbounded $ replicateM_ n . sink1 f+sink :: Int -> (a -> IO ()) -> CoCommitter IO a+sink n f = sinkWith Unbounded n f +-- | Create a finite Committer Queue.+sinkWith :: Queue a -> Int -> (a -> IO ()) -> CoCommitter IO a+sinkWith q n f = commitQ q $ replicateM_ n . sink1 f+ -- singleton source source1 :: (Monad m) => m a -> Committer m a -> m () source1 a c = do a' <- a void $ commit c a' --- | Create a finite Emitter.+-- | Create a finite (Co)Emitter Unbounded Queue. -- -- >>> glue toStdout <$|> source 2 (pure "hi") -- hi -- hi-source :: (MonadConc m) => Int -> m a -> CoEmitter m a-source n f = emitQ Unbounded $ replicateM_ n . source1 f+source :: Int -> IO a -> CoEmitter IO a+source n f = sourceWith Unbounded n f +-- | Create a finite (Co)Emitter Unbounded Queue.+--+-- >>> glue toStdout <$|> sourceWith Single 2 (pure "hi")+-- hi+-- hi+sourceWith :: Queue a -> Int -> IO a -> CoEmitter IO a+sourceWith q n f = emitQ q $ replicateM_ n . source1 f+ -- | Glues an emitter to a committer, then resupplies the emitter. -- -- >>> (c1,l1) <- refCommitter :: IO (Committer IO Int, IO [Int])@@ -119,11 +145,11 @@ pure a -- | Buffer a committer.-bufferCommitter :: (MonadConc m) => Committer m a -> CoCommitter m a+bufferCommitter :: Committer IO a -> CoCommitter IO a bufferCommitter c = Codensity $ \caction -> queueL Unbounded caction (glue c) -- | Buffer an emitter.-bufferEmitter :: (MonadConc m) => Emitter m a -> CoEmitter m a+bufferEmitter :: Emitter IO a -> CoEmitter IO a bufferEmitter e = Codensity $ \eaction -> queueR Unbounded (`glue` e) eaction -- | Concurrently run two emitters.@@ -140,13 +166,12 @@ -- > (c,l) <- refCommitter :: IO (Committer IO Int, IO [Int]) -- > close $ glue c <$> (join $ concurrentE Single <$> qList [1..30] <*> qList [40..60]) concurrentE ::- MonadConc f => Queue a ->- Emitter f a ->- Emitter f a ->- CoEmitter f a+ Emitter IO a ->+ Emitter IO a ->+ CoEmitter IO a concurrentE q e e' =- Codensity $ \eaction -> snd . fst <$> C.concurrently (queue q (`glue` e) eaction) (queue q (`glue` e') eaction)+ Codensity $ \eaction -> snd . fst <$> concurrently (queue q (`glue` e) eaction) (queue q (`glue` e') eaction) -- | Concurrently run two committers. --@@ -161,26 +186,45 @@ -- slow: 1 -- slow: 2 -- slow: 3-concurrentC :: (MonadConc m) => Queue a -> Committer m a -> Committer m a -> CoCommitter m a+concurrentC :: Queue a -> Committer IO a -> Committer IO a -> CoCommitter IO a concurrentC q c c' = mergeC <$> eitherC q c c' eitherC ::- (MonadConc m) => Queue a ->- Committer m a ->- Committer m a ->- Codensity m (Either (Committer m a) (Committer m a))+ Committer IO a ->+ Committer IO a ->+ Codensity IO (Either (Committer IO a) (Committer IO a)) eitherC q cl cr = Codensity $ \kk -> fst- <$> C.concurrently+ <$> concurrently (queueL q (kk . Left) (glue cl)) (queueL q (kk . Right) (glue cr)) -mergeC :: Either (Committer m a) (Committer m a) -> Committer m a+mergeC :: Either (Committer IO a) (Committer IO a) -> Committer IO a mergeC ec = Committer $ \a -> case ec of Left lc -> commit lc a Right rc -> commit rc a++-- | Take and queue n emits.+--+-- >>> import Control.Monad.State.Lazy+-- >>> toListM <$|> (takeQ Single 4 =<< qList [0..])+-- [0,1,2,3]+takeQ :: Queue a -> Int -> Emitter IO a -> CoEmitter IO a+takeQ q n e = emitQ q $ \c -> glueES 0 c (takeE n e)++-- | queue a stateful emitter, supplying initial state+--+-- >>> import Control.Monad.State.Lazy+-- >>> toListM <$|> (evalEmitter 0 <$> takeE 4 =<< qList [0..])+-- [0,1,2,3]+evalEmitter :: s -> Emitter (StateT s IO) a -> CoEmitter IO a+evalEmitter s e = evalEmitterWith Unbounded s e++-- | queue a stateful emitter, supplying initial state+evalEmitterWith :: Queue a -> s -> Emitter (StateT s IO) a -> CoEmitter IO a+evalEmitterWith q s e = emitQ q $ \c -> glueES s c e
src/Box/Emitter.hs view
@@ -7,6 +7,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} {-# OPTIONS_GHC -fno-warn-type-defaults #-} -- | `emit`@@ -19,6 +20,7 @@ unlistE, takeE, takeUntilE,+ dropE, pop, ) where@@ -181,6 +183,16 @@ takeE :: (Monad m) => Int -> Emitter m a -> Emitter (StateT Int m) a takeE n (Emitter e) = Emitter $ get >>= \n' -> bool (pure Nothing) (put (n' + 1) >> lift e) (n' < n)++-- | Drop n emits.+--+-- >>> import Control.Monad.State.Lazy+-- >>> toListM <$|> (dropE 2 =<< qList [0..3])+-- [2,3]+dropE :: (Monad m) => Int -> Emitter m a -> CoEmitter m a+dropE n e = Codensity $ \k -> do+ replicateM_ n (emit e)+ k e -- | Take from an emitter until a predicate. --
src/Box/IO.hs view
@@ -16,13 +16,22 @@ toStdoutN, readStdin, showStdout,- handleE,- handleC, refCommitter, refEmitter,+ handleE,+ handleC, fileE,- fileWriteC,- fileAppendC,+ fileC,+ fileEText,+ fileEBS,+ fileCText,+ fileCBS,+ logConsoleC,+ logConsoleE,+ pauser,+ changer,+ quit,+ restart, ) where @@ -30,14 +39,18 @@ import Box.Committer import Box.Connectors import Box.Emitter-import qualified Control.Concurrent.Classy.IORef as C+import Control.Concurrent.Async import Control.Exception-import qualified Control.Monad.Conc.Class as C+import Control.Monad.State.Lazy import Data.Bool+import Data.ByteString.Char8 as Char8 import Data.Foldable+import Data.Function import Data.Functor.Contravariant+import Data.IORef import qualified Data.Sequence as Seq-import Data.Text as Text+import Data.String+import Data.Text as Text hiding (null) import Data.Text.IO as Text import System.IO as IO import Prelude@@ -81,9 +94,11 @@ fromStdinN :: Int -> CoEmitter IO Text fromStdinN n = source n Text.getLine +-- FIXME: This doctest sometimes fails with the last value not being printed. Hypothesis: the pipe collapses before the console print effect happens.+ -- | Finite console committer ----- >>> glue <$> contramap (pack . show) <$> (toStdoutN 2) <*|> qList [1..3]+-- > glue <$> contramap (pack . show) <$> (toStdoutN 2) <*|> qList [1..3] -- 1 -- 2 toStdoutN :: Int -> CoCommitter IO Text@@ -96,6 +111,7 @@ -- 1 -- hippo -- 2+-- 2 readStdin :: Read a => Emitter IO a readStdin = witherE (pure . either (const Nothing) Just) . readE $ fromStdin @@ -109,44 +125,77 @@ showStdout = contramap (Text.pack . show) toStdout -- | Emits lines of Text from a handle.-handleE :: Handle -> Emitter IO Text-handleE h = Emitter $ do- l :: (Either IOException Text) <- try (Text.hGetLine h)+-- handleEText = handleE Text.hGetLine++-- handleEBS = handleE Char8.hGetLine++-- | Emits lines of Text from a handle.+handleE :: (IsString a, Eq a) => (Handle -> IO a) -> Handle -> Emitter IO a+handleE action h = Emitter $ do+ l :: (Either IOException a) <- try (action h) pure $ case l of Left _ -> Nothing Right a -> bool (Just a) Nothing (a == "") -- | Commit lines of Text to a handle.-handleC :: Handle -> Committer IO Text-handleC h = Committer $ \a -> do- Text.hPutStrLn h a+handleC :: (Handle -> a -> IO ()) -> Handle -> Committer IO a+handleC action h = Committer $ \a -> do+ action h a pure True +-- | Commit lines of Text to a handle.+-- handleCBS = handleC Char8.hPutStrLn++-- | Emits lines of Text from a handle.+-- handleCText = handleC Text.hPutStrLn+ -- | Emit lines of Text from a file.-fileE :: FilePath -> CoEmitter IO Text-fileE fp = Codensity $ \eio -> withFile fp ReadMode (eio . handleE)+fileE :: FilePath -> BufferMode -> IOMode -> (Handle -> Emitter IO a) -> CoEmitter IO a+fileE fp b m action = Codensity $ \eio ->+ withFile+ fp+ m+ ( \h -> do+ hSetBuffering h b+ eio (action h)+ ) +fileEText :: FilePath -> BufferMode -> CoEmitter IO Text+fileEText fp b = fileE fp b ReadMode (handleE Text.hGetLine)++fileEBS :: FilePath -> BufferMode -> CoEmitter IO ByteString+fileEBS fp b = fileE fp b ReadMode (handleE Char8.hGetLine)+ -- | Commit lines of Text to a file.-fileWriteC :: FilePath -> CoCommitter IO Text-fileWriteC fp = Codensity $ \cio -> withFile fp WriteMode (cio . handleC)+fileC :: FilePath -> IOMode -> BufferMode -> (Handle -> Committer IO a) -> CoCommitter IO a+fileC fp m b action = Codensity $ \cio ->+ withFile+ fp+ m+ ( \h -> do+ hSetBuffering h b+ cio (action h)+ ) --- | Commit lines of Text, appending to a file.-fileAppendC :: FilePath -> CoCommitter IO Text-fileAppendC fp = Codensity $ \cio -> withFile fp AppendMode (cio . handleC)+fileCText :: FilePath -> BufferMode -> IOMode -> CoCommitter IO Text+fileCText fp m b = fileC fp b m (handleC Text.hPutStrLn) +fileCBS :: FilePath -> BufferMode -> IOMode -> CoCommitter IO ByteString+fileCBS fp m b = fileC fp b m (handleC Char8.hPutStrLn)+ -- | Commit to an IORef -- -- >>> (c1,l1) <- refCommitter :: IO (Committer IO Int, IO [Int]) -- >>> glue c1 <$|> qList [1..3] -- >>> l1 -- [1,2,3]-refCommitter :: (C.MonadConc m) => m (Committer m a, m [a])+refCommitter :: IO (Committer IO a, IO [a]) refCommitter = do- ref <- C.newIORef Seq.empty+ ref <- newIORef Seq.empty let c = Committer $ \a -> do- C.modifyIORef ref (Seq.:|> a)+ modifyIORef ref (Seq.:|> a) pure True- let res = toList <$> C.readIORef ref+ let res = toList <$> readIORef ref pure (c, res) -- | Emit from a list IORef@@ -154,14 +203,77 @@ -- >>> e <- refEmitter [1..3] -- >>> toListM e -- [1,2,3]-refEmitter :: (C.MonadConc m) => [a] -> m (Emitter m a)+refEmitter :: [a] -> IO (Emitter IO a) refEmitter xs = do- ref <- C.newIORef xs+ ref <- newIORef xs let e = Emitter $ do- as <- C.readIORef ref+ as <- readIORef ref case as of [] -> pure Nothing (x : xs') -> do- C.writeIORef ref xs'+ writeIORef ref xs' pure $ Just x pure e++-- | simple console logger for rough testing+logConsoleE :: Show a => String -> Emitter IO a -> Emitter IO a+logConsoleE label e = Emitter $ do+ a <- emit e+ Prelude.putStrLn (label <> show a)+ pure a++-- | simple console logger for rough testing+logConsoleC :: Show a => String -> Committer IO a -> Committer IO a+logConsoleC label c = Committer $ \a -> do+ Prelude.putStrLn (label <> show a)+ commit c a++-- | Pause an emitter based on a Bool emitter+pauser :: Emitter IO Bool -> Emitter IO a -> Emitter IO a+pauser b e = Emitter $ fix $ \rec -> do+ b' <- emit b+ case b' of+ Nothing -> pure Nothing+ Just False -> emit e+ Just True -> rec++-- | Create an emitter that indicates when another emitter has changed.+changer :: (Eq a) => a -> Emitter IO a -> CoEmitter IO Bool+changer a0 e = evalEmitter a0 $ Emitter $ do+ r <- lift $ emit e+ case r of+ Nothing -> pure Nothing+ Just r' -> do+ r'' <- get+ put r'+ pure (Just (r' == r''))++-- | quit a process based on a Bool emitter+--+-- > quit <$> speedEffect (pure 2) <$> (resetGap 5) <*|> pure io+-- 0+-- 1+-- 2+-- 3+-- 4+-- Left True+quit :: Emitter IO Bool -> IO a -> IO (Either Bool a)+quit flag io = race (checkE flag) io++checkE :: Emitter IO Bool -> IO Bool+checkE e = fix $ \rec -> do+ a <- emit e+ -- atomically $ check (a == Just False)+ case a of+ Nothing -> pure False+ Just True -> pure True+ Just False -> rec++-- | restart a process if flagged by a Bool emitter+restart :: Emitter IO Bool -> IO a -> IO (Either Bool a)+restart flag io = fix $ \rec -> do+ res <- quit flag io+ case res of+ Left True -> rec+ Left False -> pure (Left False)+ Right r -> pure (Right r)
src/Box/Queue.hs view
@@ -1,7 +1,6 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StrictData #-}@@ -18,6 +17,9 @@ fromAction, emitQ, commitQ,+ fromActionWith,+ toBoxM,+ toBoxSTM, ) where @@ -27,10 +29,9 @@ import Box.Emitter import Box.Functor import Control.Applicative-import Control.Concurrent.Classy.Async as C-import Control.Concurrent.Classy.STM as C+import Control.Concurrent.Async+import Control.Concurrent.STM import Control.Monad.Catch as C-import Control.Monad.Conc.Class as C import Prelude -- $setup@@ -48,7 +49,7 @@ | New -- | create a queue, supplying the ends and a sealing function.-ends :: MonadSTM stm => Queue a -> stm (a -> stm (), stm a)+ends :: Queue a -> STM (a -> STM (), STM a) ends qu = case qu of Bounded n -> do@@ -72,7 +73,7 @@ pure (write, readTBQueue q) -- | write to a queue, checking the seal-writeCheck :: (MonadSTM stm) => TVar stm Bool -> (a -> stm ()) -> a -> stm Bool+writeCheck :: TVar Bool -> (a -> STM ()) -> a -> STM Bool writeCheck sealed i a = do b <- readTVar sealed if b@@ -82,23 +83,22 @@ pure True -- | read from a queue, and retry if not sealed-readCheck :: MonadSTM stm => TVar stm Bool -> stm a -> stm (Maybe a)+readCheck :: TVar Bool -> STM a -> STM (Maybe a) readCheck sealed o = (Just <$> o) <|> ( do b <- readTVar sealed- C.check b+ check b pure Nothing ) -- | turn a queue into a box (and a seal) toBoxSTM ::- (MonadSTM stm) => Queue a ->- stm (Box stm a a, stm ())+ STM (Box STM a a, STM ()) toBoxSTM q = do (i, o) <- ends q- sealed <- newTVarN "sealed" False+ sealed <- newTVar False let seal = writeTVar sealed True pure ( Box@@ -109,90 +109,85 @@ -- | turn a queue into a box (and a seal), and lift from stm to the underlying monad. toBoxM ::- (MonadConc m) => Queue a ->- m (Box m a a, m ())+ IO (Box IO a a, IO ()) toBoxM q = do (b, s) <- atomically $ toBoxSTM q pure (liftB b, atomically s) -- | run two actions concurrently, but wait and return on the left result.-concurrentlyLeft :: MonadConc m => m a -> m b -> m a+concurrentlyLeft :: IO a -> IO b -> IO a concurrentlyLeft left right =- C.withAsync left $ \a ->- C.withAsync right $ \_ ->- C.wait a+ withAsync left $ \a ->+ withAsync right $ \_ ->+ wait a -- | run two actions concurrently, but wait and return on the right result.-concurrentlyRight :: MonadConc m => m a -> m b -> m b+concurrentlyRight :: IO a -> IO b -> IO b concurrentlyRight left right =- C.withAsync left $ \_ ->- C.withAsync right $ \b ->- C.wait b+ withAsync left $ \_ ->+ withAsync right $ \b ->+ wait b -- | connect a committer and emitter action via spawning a queue, and wait for the Committer action to complete. withQL ::- (MonadConc m) => Queue a ->- (Queue a -> m (Box m a a, m ())) ->- (Committer m a -> m l) ->- (Emitter m a -> m r) ->- m l+ (Queue a -> IO (Box IO a a, IO ())) ->+ (Committer IO a -> IO l) ->+ (Emitter IO a -> IO r) ->+ IO l withQL q spawner cio eio =- C.bracket+ bracket (spawner q) snd ( \(box, seal) -> concurrentlyLeft- (cio (committer box) `C.finally` seal)- (eio (emitter box) `C.finally` seal)+ (cio (committer box) `finally` seal)+ (eio (emitter box) `finally` seal) ) -- | connect a committer and emitter action via spawning a queue, and wait for the Emitter action to complete. withQR ::- (MonadConc m) => Queue a ->- (Queue a -> m (Box m a a, m ())) ->- (Committer m a -> m l) ->- (Emitter m a -> m r) ->- m r+ (Queue a -> IO (Box IO a a, IO ())) ->+ (Committer IO a -> IO l) ->+ (Emitter IO a -> IO r) ->+ IO r withQR q spawner cio eio =- C.bracket+ bracket (spawner q) snd ( \(box, seal) -> concurrentlyRight- (cio (committer box) `C.finally` seal)- (eio (emitter box) `C.finally` seal)+ (cio (committer box) `finally` seal)+ (eio (emitter box) `finally` seal) ) -- | connect a committer and emitter action via spawning a queue, and wait for both to complete. withQ ::- (MonadConc m) => Queue a ->- (Queue a -> m (Box m a a, m ())) ->- (Committer m a -> m l) ->- (Emitter m a -> m r) ->- m (l, r)+ (Queue a -> IO (Box IO a a, IO ())) ->+ (Committer IO a -> IO l) ->+ (Emitter IO a -> IO r) ->+ IO (l, r) withQ q spawner cio eio =- C.bracket+ bracket (spawner q) snd ( \(box, seal) -> concurrently- (cio (committer box) `C.finally` seal)- (eio (emitter box) `C.finally` seal)+ (cio (committer box) `finally` seal)+ (eio (emitter box) `finally` seal) ) -- | Create an unbounded queue, returning the result from the Committer action. -- -- >>> queueL New (\c -> glue c <$|> qList [1..3]) toListM queueL ::- (MonadConc m) => Queue a ->- (Committer m a -> m l) ->- (Emitter m a -> m r) ->- m l+ (Committer IO a -> IO l) ->+ (Emitter IO a -> IO r) ->+ IO l queueL q cm em = withQL q toBoxM cm em -- | Create an unbounded queue, returning the result from the Emitter action.@@ -200,11 +195,10 @@ -- >>> queueR New (\c -> glue c <$|> qList [1..3]) toListM -- [3] queueR ::- (MonadConc m) => Queue a ->- (Committer m a -> m l) ->- (Emitter m a -> m r) ->- m r+ (Committer IO a -> IO l) ->+ (Emitter IO a -> IO r) ->+ IO r queueR q cm em = withQR q toBoxM cm em -- | Create an unbounded queue, returning both results.@@ -212,32 +206,42 @@ -- >>> queue Unbounded (\c -> glue c <$|> qList [1..3]) toListM -- ((),[1,2,3]) queue ::- (MonadConc m) => Queue a ->- (Committer m a -> m l) ->- (Emitter m a -> m r) ->- m (l, r)+ (Committer IO a -> IO l) ->+ (Emitter IO a -> IO r) ->+ IO (l, r) queue q cm em = withQ q toBoxM cm em -- | lift a box from STM-liftB :: (MonadConc m) => Box (STM m) a b -> Box m a b+liftB :: Box STM a b -> Box IO a b liftB (Box c e) = Box (foist atomically c) (foist atomically e) -- | Turn a box action into a box continuation-fromAction :: (MonadConc m) => (Box m a b -> m r) -> CoBox m b a+fromAction :: (Box IO a b -> IO r) -> CoBox IO b a fromAction baction = Codensity $ fuseActions baction --- | Connect up two box actions via two queues-fuseActions :: (MonadConc m) => (Box m a b -> m r) -> (Box m b a -> m r') -> m r'+-- | Turn a box action into a box continuation+fromActionWith :: Queue a -> Queue b -> (Box IO a b -> IO r) -> CoBox IO b a+fromActionWith qa qb baction = Codensity $ fuseActionsWith qa qb baction++-- | Connect up two box actions via two Unbounded queues+fuseActions :: (Box IO a b -> IO r) -> (Box IO b a -> IO r') -> IO r' fuseActions abm bam = do (Box ca ea, _) <- toBoxM Unbounded (Box cb eb, _) <- toBoxM Unbounded concurrentlyRight (abm (Box ca eb)) (bam (Box cb ea)) +-- | Connect up two box actions via two queues+fuseActionsWith :: Queue a -> Queue b -> (Box IO a b -> IO r) -> (Box IO b a -> IO r') -> IO r'+fuseActionsWith qa qb abm bam = do+ (Box ca ea, _) <- toBoxM qa+ (Box cb eb, _) <- toBoxM qb+ concurrentlyRight (abm (Box ca eb)) (bam (Box cb ea))+ -- | Hook a committer action to a queue, creating an emitter continuation.-emitQ :: (MonadConc m) => Queue a -> (Committer m a -> m r) -> CoEmitter m a+emitQ :: Queue a -> (Committer IO a -> IO r) -> CoEmitter IO a emitQ q cio = Codensity $ \eio -> queueR q cio eio -- | Hook a committer action to a queue, creating an emitter continuation.-commitQ :: (MonadConc m) => Queue a -> (Emitter m a -> m r) -> CoCommitter m a+commitQ :: Queue a -> (Emitter IO a -> IO r) -> CoCommitter IO a commitQ q eio = Codensity $ \cio -> queueL q cio eio
src/Box/Time.hs view
@@ -10,19 +10,28 @@ -- | Timing effects. module Box.Time ( sleep,- Stamped (..), stampNow, stampE,- emitIn,+ Gap,+ gaps,+ fromGaps,+ fromGapsNow,+ gapEffect,+ skip, replay,+ gapSkipEffect,+ speedEffect,+ speedSkipEffect, ) where -import Box.Codensity+import Box.Connectors import Box.Emitter import Control.Applicative-import Control.Monad.Conc.Class as C-import Control.Monad.IO.Class+import Control.Concurrent+import Control.Monad.State.Lazy+import Data.Bifunctor+import Data.Bool import Data.Fixed (Fixed (MkFixed)) import Data.Time import Prelude@@ -33,8 +42,8 @@ -- >>> import Prelude -- | Sleep for x seconds.-sleep :: (MonadConc m) => Double -> m ()-sleep x = C.threadDelay (floor $ x * 1e6)+sleep :: Double -> IO ()+sleep x = threadDelay (floor $ x * 1e6) -- | convenience conversion to Double fromNominalDiffTime :: NominalDiffTime -> Double@@ -52,58 +61,136 @@ t1 = UTCTime (addDays days d0) (picosecondsToDiffTime $ floor (secs / 1.0e-12)) in diffUTCTime t1 t0 --- | A value with a UTCTime annotation.-data Stamped a = Stamped- { stamp :: !UTCTime,- value :: !a- }- deriving (Eq, Show, Read)- -- | Add the current time-stampNow :: (MonadConc m, MonadIO m) => a -> m (LocalTime, a)+stampNow :: a -> IO (LocalTime, a) stampNow a = do- t <- liftIO getCurrentTime+ t <- getCurrentTime pure (utcToLocalTime utc t, a) -- | Add the current time stamp. -- -- @--- > process (toListM . stampE) (qList [1..3])--- [(2022-02-09 01:18:00.293883,1),(2022-02-09 01:18:00.293899,2),(2022-02-09 01:18:00.293903,3)]+-- > toListM . stampE <$|> (qList [1..3])+-- [(2022-08-30 01:55:16.517127,1),(2022-08-30 01:55:16.517132,2),(2022-08-30 01:55:16.517135,3)] -- @ stampE ::- (MonadConc m, MonadIO m) =>- Emitter m a ->- Emitter m (LocalTime, a)+ Emitter IO a ->+ Emitter IO (LocalTime, a) stampE = witherE (fmap Just . stampNow) --- | Wait s seconds before emitting-emitIn ::- Emitter IO (Double, a) ->- Emitter IO a-emitIn =- witherE- ( \(s, a) -> do- sleep s- pure $ Just a- )+type Gap = Double --- | Convert emitter stamps to adjusted speed delays-delay :: (Monad m, Alternative m) => Double -> Emitter m (LocalTime, b) -> m (Emitter m (Double, b))-delay speed e = do- r <- emit e+-- | Convert stamped emitter to gap between emits in seconds+--+-- > toListM <$|> (gaps =<< (fromGapsNow =<< (qList (zip (0:repeat 1) [1..4]))))+-- [(0.0,1),(1.0,2),(1.0,3),(1.0,4)]+gaps :: Emitter IO (LocalTime, a) -> CoEmitter IO (Gap, a)+gaps e = evalEmitter Nothing $ Emitter $ do+ r <- lift $ emit e case r of- Nothing -> pure mempty- Just (t0, _) -> do- let delta u = fromNominalDiffTime $ diffLocalTime u t0 * toNominalDiffTime speed- pure (witherE (\(l, a) -> pure (Just (delta l, a))) e)+ Nothing -> pure Nothing+ Just a' -> do+ t' <- get+ let delta u = maybe 0 (fromNominalDiffTime . diffLocalTime u) t'+ put (Just $ fst a')+ pure $ Just $ first delta a' +-- | Convert gaps in seconds to stamps starting from an initial supplied 'LocalTime'+fromGaps :: LocalTime -> Emitter IO (Gap, a) -> CoEmitter IO (LocalTime, a)+fromGaps t0 e = evalEmitter t0 $ Emitter $ do+ r <- lift $ emit e+ case r of+ Nothing -> pure Nothing+ Just a' -> do+ t' <- get+ let t'' = addLocalTime (toNominalDiffTime (fst a')) t'+ put t''+ pure $ Just (t'', snd a')++-- | Convert gaps in seconds to stamps starting with current time+--+-- > toListM <$|> (fromGapsNow =<< (qList (zip (0:repeat 1) [1..4])))+-- [(2022-08-30 22:57:33.835228,1),(2022-08-30 22:57:34.835228,2),(2022-08-30 22:57:35.835228,3),(2022-08-30 22:57:36.835228,4)]+fromGapsNow :: Emitter IO (Gap, a) -> CoEmitter IO (LocalTime, a)+fromGapsNow e = do+ t0 <- liftIO getCurrentTime+ fromGaps (utcToLocalTime utc t0) e++-- | Convert a (Gap,a) emitter to an a emitter, with delays between emits of the gap.+gapEffect ::+ Emitter IO (Gap, a) ->+ Emitter IO a+gapEffect as =+ Emitter $ do+ a <- emit as+ case a of+ (Just (s, a')) -> sleep s >> pure (Just a')+ _ -> pure Nothing++speedEffect ::+ Emitter IO Gap ->+ Emitter IO (Gap, a) ->+ Emitter IO a+speedEffect speeds as =+ Emitter $ do+ s <- emit speeds+ a <- emit as+ case (s, a) of+ (Just s', Just (g, a')) -> sleep (g / s') >> pure (Just a')+ _ -> pure Nothing++-- | Only add a Gap effect if greater than the Int emitter+--+-- effect is similar to a fast-forward of the first n emits+gapSkipEffect ::+ Emitter IO Int ->+ Emitter IO Gap ->+ CoEmitter IO Gap+gapSkipEffect n e = evalEmitter 0 $ Emitter $ do+ n' <- lift $ emit n+ e' <- lift $ emit e+ count <- get+ modify (1 +)+ case (n', e') of+ (_, Nothing) -> pure Nothing+ (Nothing, _) -> pure Nothing+ (Just n'', Just e'') ->+ pure $ Just (bool e'' 0 (n'' >= count))++-- | Only add a Gap if greater than the Int emitter+--+-- effect is similar to a fast-forward of the first n emits+speedSkipEffect ::+ Emitter IO (Int, Gap) ->+ Emitter IO (Gap, a) ->+ CoEmitter IO a+speedSkipEffect p e = evalEmitter 0 $ Emitter $ do+ p' <- lift $ emit p+ e' <- lift $ emit e+ count <- get+ modify (1 +)+ case (p', e') of+ (_, Nothing) -> pure Nothing+ (Nothing, _) -> pure Nothing+ (Just (n, s), Just (g, a)) ->+ lift $ sleep (bool (g / s) 0 (n >= count)) >> pure (Just a)++skip :: Int -> Emitter IO (Gap, a) -> CoEmitter IO (Gap, a)+skip sk e = evalEmitter (sk + 1) $ Emitter $ do+ skip' <- get+ e' <- lift $ emit e+ case e' of+ Nothing -> pure Nothing+ Just (secs, a) -> do+ case skip' of+ 0 -> pure (Just (secs, a))+ _ -> do+ put (skip' - 1)+ pure (Just (0, a))+ -- | Replay a stamped emitter, adjusting the speed of the replay. ----- @--- > glueN 4 showStdout <$|> replay 1 (Emitter $ sleep 0.1 >> Just <$> stampNow ())--- @-replay :: Double -> Emitter IO (LocalTime, a) -> CoEmitter IO a-replay speed e = Codensity $ \eaction -> do- e' <- delay speed e- eaction (emitIn e')+-- > toListM . stampE <$|> (replay 0.1 1 =<< (fromGapsNow =<< (qList (zip (0:repeat 1) [1..4]))))+-- [(2022-08-31 02:29:39.643831,1),(2022-08-31 02:29:39.643841,2),(2022-08-31 02:29:39.746998,3),(2022-08-31 02:29:39.849615,4)]+replay :: Double -> Int -> Emitter IO (LocalTime, a) -> CoEmitter IO a+replay speed sk e = gapEffect . fmap (first (speed *)) <$> (skip sk =<< gaps e)