packages feed

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 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)