packages feed

chan 0.0.2 → 0.0.3

raw patch · 7 files changed

+425/−1 lines, 7 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Control.Concurrent.Chan.Scope: Read :: Scope
+ Control.Concurrent.Chan.Scope: ReadWrite :: Scope
+ Control.Concurrent.Chan.Scope: Write :: Scope
+ Control.Concurrent.Chan.Scope: class Readable (a :: Scope)
+ Control.Concurrent.Chan.Scope: class Writable (a :: Scope)
+ Control.Concurrent.Chan.Scope: data Scope
+ Control.Concurrent.Chan.Scope: instance Control.Concurrent.Chan.Scope.Readable 'Control.Concurrent.Chan.Scope.Read
+ Control.Concurrent.Chan.Scope: instance Control.Concurrent.Chan.Scope.Readable 'Control.Concurrent.Chan.Scope.ReadWrite
+ Control.Concurrent.Chan.Scope: instance Control.Concurrent.Chan.Scope.Writable 'Control.Concurrent.Chan.Scope.ReadWrite
+ Control.Concurrent.Chan.Scope: instance Control.Concurrent.Chan.Scope.Writable 'Control.Concurrent.Chan.Scope.Write
+ Control.Concurrent.Chan.Typed: ChanRW :: (Chan a) -> ChanRW a
+ Control.Concurrent.Chan.Typed: allowReading :: Writable scope => ChanRW scope a -> ChanRW 'ReadWrite a
+ Control.Concurrent.Chan.Typed: allowWriting :: Readable scope => ChanRW scope a -> ChanRW 'ReadWrite a
+ Control.Concurrent.Chan.Typed: dupChanRW :: Writable scopeIn => Readable scopeOut => ChanRW scopeIn a -> IO (ChanRW scopeOut a)
+ Control.Concurrent.Chan.Typed: newChanRW :: IO (ChanRW 'ReadWrite a)
+ Control.Concurrent.Chan.Typed: newtype ChanRW (scope :: Scope) a
+ Control.Concurrent.Chan.Typed: readChanRW :: Readable scope => ChanRW scope a -> IO a
+ Control.Concurrent.Chan.Typed: readOnly :: Readable scope => ChanRW scope a -> ChanRW 'Read a
+ Control.Concurrent.Chan.Typed: writeChanRW :: Writable scope => ChanRW scope a -> a -> IO ()
+ Control.Concurrent.Chan.Typed: writeOnly :: Writable scope => ChanRW scope a -> ChanRW 'Write a
+ Control.Concurrent.Chan.Typed.Extra: debounceStatic :: DiffNanosec -> ChanRW 'Read a -> IO (ChanRW 'Write a, Async ())
+ Control.Concurrent.Chan.Typed.Extra: intersperseStatic :: DiffNanosec -> IO a -> ChanRW 'Read a -> IO (ChanRW 'Write a, Async (), Async ())
+ Control.Concurrent.Chan.Typed.Extra: throttleStatic :: DiffNanosec -> ChanRW 'Read a -> IO (ChanRW 'Write a, Async ())
+ Control.Concurrent.Chan.Typed.Extra: type DiffNanosec = Int
+ Control.Concurrent.STM.TChan.Typed: TChanRW :: (TChan a) -> TChanRW a
+ Control.Concurrent.STM.TChan.Typed: allowReading :: Writable scope => TChanRW scope a -> TChanRW 'ReadWrite a
+ Control.Concurrent.STM.TChan.Typed: allowWriting :: Readable scope => TChanRW scope a -> TChanRW 'ReadWrite a
+ Control.Concurrent.STM.TChan.Typed: cloneTChanRW :: Writable scopeIn => Readable scopeOut => TChanRW scopeIn a -> STM (TChanRW scopeOut a)
+ Control.Concurrent.STM.TChan.Typed: dupTChanRW :: Writable scopeIn => Readable scopeOut => TChanRW scopeIn a -> STM (TChanRW scopeOut a)
+ Control.Concurrent.STM.TChan.Typed: isEmptyTChanRW :: Readable scope => TChanRW scope a -> STM Bool
+ Control.Concurrent.STM.TChan.Typed: newBroadcastTChanRW :: STM (TChanRW 'Write a)
+ Control.Concurrent.STM.TChan.Typed: newTChanRW :: STM (TChanRW 'ReadWrite a)
+ Control.Concurrent.STM.TChan.Typed: newtype TChanRW (scope :: Scope) a
+ Control.Concurrent.STM.TChan.Typed: peekTChanRW :: Readable scope => TChanRW scope a -> STM a
+ Control.Concurrent.STM.TChan.Typed: readOnly :: Readable scope => TChanRW scope a -> TChanRW 'Read a
+ Control.Concurrent.STM.TChan.Typed: readTChanRW :: Readable scope => TChanRW scope a -> STM a
+ Control.Concurrent.STM.TChan.Typed: tryPeekTChanRW :: Readable scope => TChanRW scope a -> STM (Maybe a)
+ Control.Concurrent.STM.TChan.Typed: tryReadTChanRW :: Readable scope => TChanRW scope a -> STM (Maybe a)
+ Control.Concurrent.STM.TChan.Typed: unGetTChanRW :: Writable scope => TChanRW scope a -> a -> STM ()
+ Control.Concurrent.STM.TChan.Typed: writeOnly :: Writable scope => TChanRW scope a -> TChanRW 'Write a
+ Control.Concurrent.STM.TChan.Typed: writeTChanRW :: Writable scope => TChanRW scope a -> a -> STM ()
+ Control.Concurrent.STM.TChan.Typed.Extra: debounceStatic :: DiffNanosec -> TChanRW 'Read a -> IO (TChanRW 'Write a, Async ())
+ Control.Concurrent.STM.TChan.Typed.Extra: intersperseStatic :: DiffNanosec -> IO a -> TChanRW 'Read a -> IO (TChanRW 'Write a, Async (), Async ())
+ Control.Concurrent.STM.TChan.Typed.Extra: throttleStatic :: DiffNanosec -> TChanRW 'Read a -> IO (TChanRW 'Write a, Async ())
+ Control.Concurrent.STM.TChan.Typed.Extra: type DiffNanosec = Int

Files

README.md view
@@ -1,1 +1,41 @@ # chan++This is just some extra `Chan` and `TChan` kit that might help the average user. It relies on+spawning threads with `async` and either canceling (debouncing) or waiting (throttling) messages.++Unfortunately, the current design is untyped in the sense that the channel which you supply is the+_output_ channel, and the returned channel is the one you would write to. I'm not sure how this should+be fixed.++An example might be the following:++```haskell+import Control.Concurrent.Chan (readChan)+import Control.Concurrent.Chan.Extra (throttleStatic, intersperseStatic)++++-- For example, some websockets:++data SomeMessage+  = Ping+  -- | ...++throttleLayer :: Chan SomeMessage -> IO (Chan SomeMessage)+throttleLayer output = do+  (x,_) <- throttleStatic output 1000000 -- nanoseconds, = 1 second+  pure x++pingLayer :: Chan SomeMessage -> IO (Chan SomeMessage)+pingLayer output = do+  (x,_,_) <- intersperseStatic output (pure Ping) 1000000+  pure x++performWebsocket :: Chan SomeMessage -> IO ()+performWebsocket output = do+  output' <- pingLayer =<< throttleLayer output+  _ <- async $ forever $ do+    msg <- readChan output'+    send msg -- something like that - it'll include Ping messages for us,+             -- and throttle the outgoing messages at the same time.+```
chan.cabal view
@@ -1,5 +1,5 @@ name:                chan-version:             0.0.2+version:             0.0.3 synopsis:            Some extra kit for Chans -- description: homepage:            https://github.com/athanclark/chan#readme@@ -16,7 +16,12 @@ library   hs-source-dirs:      src   exposed-modules:     Control.Concurrent.Chan.Extra+                       Control.Concurrent.Chan.Scope+                       Control.Concurrent.Chan.Typed+                       Control.Concurrent.Chan.Typed.Extra                        Control.Concurrent.STM.TChan.Extra+                       Control.Concurrent.STM.TChan.Typed+                       Control.Concurrent.STM.TChan.Typed.Extra   build-depends:       base >= 4.7 && < 5                      , async                      , stm
+ src/Control/Concurrent/Chan/Scope.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE+    DataKinds+  , KindSignatures+  #-}++module Control.Concurrent.Chan.Scope where+++data Scope = Read | Write | ReadWrite+++class Readable (a :: Scope) where++instance Readable 'Read where++instance Readable 'ReadWrite where+++class Writable (a :: Scope) where++instance Writable 'Write where++instance Writable 'ReadWrite where
+ src/Control/Concurrent/Chan/Typed.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE+    DataKinds+  , KindSignatures+  #-}++module Control.Concurrent.Chan.Typed where++import Control.Concurrent.Chan.Scope (Scope (..), Writable, Readable)+import Control.Concurrent.Chan (Chan)+import qualified Control.Concurrent.Chan as Chan+++newtype ChanRW (scope :: Scope) a = ChanRW (Chan a)+++readOnly :: Readable scope => ChanRW scope a -> ChanRW 'Read a+readOnly (ChanRW c) = ChanRW c++writeOnly :: Writable scope => ChanRW scope a -> ChanRW 'Write a+writeOnly (ChanRW c) = ChanRW c++allowReading :: Writable scope => ChanRW scope a -> ChanRW 'ReadWrite a+allowReading (ChanRW c) = ChanRW c++allowWriting :: Readable scope => ChanRW scope a -> ChanRW 'ReadWrite a+allowWriting (ChanRW c) = ChanRW c+++newChanRW :: IO (ChanRW 'ReadWrite a)+newChanRW = ChanRW <$> Chan.newChan+++writeChanRW :: Writable scope => ChanRW scope a -> a -> IO ()+writeChanRW (ChanRW c) x = Chan.writeChan c x+++readChanRW :: Readable scope => ChanRW scope a -> IO a+readChanRW (ChanRW c) = Chan.readChan c+++dupChanRW :: Writable scopeIn+          => Readable scopeOut+          => ChanRW scopeIn a -> IO (ChanRW scopeOut a)+dupChanRW (ChanRW c) = ChanRW <$> Chan.dupChan c
+ src/Control/Concurrent/Chan/Typed/Extra.hs view
@@ -0,0 +1,133 @@+{-# LANGUAGE+    DataKinds+  #-}++module Control.Concurrent.Chan.Typed.Extra where++import Data.IORef (newIORef, readIORef, writeIORef)+-- import Data.Time.Since (timeSince, newTimeSince)+import Control.Monad (forever)+import Control.Concurrent (threadDelay)+import Control.Concurrent.MVar (newEmptyMVar, tryTakeMVar, putMVar)+import Control.Concurrent.Chan.Scope (Scope (..))+import Control.Concurrent.Chan.Typed (ChanRW, readChanRW, writeChanRW, newChanRW, writeOnly, allowWriting)+import Control.Concurrent.Async (Async, async, cancel, wait)++++type DiffNanosec = Int++-- type TotalNanosec = Int+++-- debounceDynamic :: (NominalDiffTime -> NominalDiffTime) -> Chan a -> IO (Chan a, Async ())+-- debounceDynamic toWait outputChan = do+--   sinceRef <- newTimeSince+--   totalWaited <- newIORef 0+--   presentedChan <- newChan+--   writingThread <- newEmptyMVar++--   let invokeWrite x = do+--         putStrLn "Being run.."++--         waited <- readIORef totalWaited+--         let toWaitFurther = toWait waited+--         writeIORef totalWaited (waited + toWaitFurther)++--         -- FIXME must use clocktime http://hackage.haskell.org/package/chan- overlayed invocations have+--         -- no concept of time spent, only have knoweldge of invocations+--         -- made++--         threadDelay toWaitFurther+--         putStrLn "waited done"+--         writeChan outputChan x++--   writer <- async $ forever $ do+--     x <- readChan presentedChan++--     newWriter <- async (invokeWrite x)++--     mInvoker <- tryTakeMVar writingThread+--     case mInvoker of+--       Nothing -> pure ()+--       Just i -> cancel i+--     print "killed"+--     putMVar writingThread newWriter++--   pure (presentedChan, writer)++debounceStatic :: DiffNanosec -> ChanRW 'Read a -> IO (ChanRW 'Write a, Async ())+debounceStatic toWaitFurther outputChan = do+  presentedChan <- newChanRW+  writingThread <- newEmptyMVar++  let invokeWrite x = do+        threadDelay toWaitFurther+        writeChanRW (allowWriting outputChan) x++  writer <- async $ forever $ do+    x <- readChanRW presentedChan++    newWriter <- async (invokeWrite x)++    mInvoker <- tryTakeMVar writingThread+    case mInvoker of+      Nothing -> pure ()+      Just i -> cancel i+    putMVar writingThread newWriter++  pure (writeOnly presentedChan, writer)+++-- | Like debounce, but lossless+throttleStatic :: DiffNanosec -> ChanRW 'Read a -> IO (ChanRW 'Write a, Async ())+throttleStatic toWaitFurther outputChan = do+  presentedChan <- newChanRW+  writingThread <- newEmptyMVar++  let invokeWrite x = do+        threadDelay toWaitFurther+        writeChanRW (allowWriting outputChan) x++  writer <- async $ forever $ do+    x <- readChanRW presentedChan++    mInvoker <- tryTakeMVar writingThread+    case mInvoker of+      Nothing -> pure ()+      Just i -> wait i+    newWriter <- async (invokeWrite x)+    putMVar writingThread newWriter++  pure (writeOnly presentedChan, writer)+++intersperseStatic :: DiffNanosec -> IO a -> ChanRW 'Read a -> IO (ChanRW 'Write a, Async (), Async ())+intersperseStatic timeBetween xM outputChan = do+  presentedChan <- newChanRW+  writingThread <- newEmptyMVar++  let invokeWritePing = do+        threadDelay timeBetween+        x <- xM+        writeChanRW (allowWriting outputChan) x++  writer <- async $ forever $ do+    mInvoker <- tryTakeMVar writingThread+    case mInvoker of+      Nothing -> pure ()+      Just i -> wait i+    newWriter <- async invokeWritePing+    putMVar writingThread newWriter++  listener <- async $ forever $ do+    y <- readChanRW presentedChan++    mInvoker <- tryTakeMVar writingThread+    case mInvoker of+      Nothing -> pure ()+      Just i -> cancel i++    writeChanRW (allowWriting outputChan) y++  pure (writeOnly presentedChan, writer, listener)
+ src/Control/Concurrent/STM/TChan/Typed.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE+    DataKinds+  , KindSignatures+  #-}++module Control.Concurrent.STM.TChan.Typed where++import Control.Concurrent.Chan.Scope (Scope (..), Writable, Readable)+import Control.Concurrent.STM.TChan (TChan)+import qualified Control.Concurrent.STM.TChan as TChan+import Control.Concurrent.STM (STM)+++newtype TChanRW (scope :: Scope) a = TChanRW (TChan a)+++readOnly :: Readable scope => TChanRW scope a -> TChanRW 'Read a+readOnly (TChanRW c) = TChanRW c++writeOnly :: Writable scope => TChanRW scope a -> TChanRW 'Write a+writeOnly (TChanRW c) = TChanRW c++allowReading :: Writable scope => TChanRW scope a -> TChanRW 'ReadWrite a+allowReading (TChanRW c) = TChanRW c++allowWriting :: Readable scope => TChanRW scope a -> TChanRW 'ReadWrite a+allowWriting (TChanRW c) = TChanRW c+++newTChanRW :: STM (TChanRW 'ReadWrite a)+newTChanRW = TChanRW <$> TChan.newTChan+++writeTChanRW :: Writable scope => TChanRW scope a -> a -> STM ()+writeTChanRW (TChanRW c) x = TChan.writeTChan c x+++unGetTChanRW :: Writable scope => TChanRW scope a -> a -> STM ()+unGetTChanRW (TChanRW c) x = TChan.unGetTChan c x+++isEmptyTChanRW :: Readable scope => TChanRW scope a -> STM Bool+isEmptyTChanRW (TChanRW c) = TChan.isEmptyTChan c+++readTChanRW :: Readable scope => TChanRW scope a -> STM a+readTChanRW (TChanRW c) = TChan.readTChan c+++tryReadTChanRW :: Readable scope => TChanRW scope a -> STM (Maybe a)+tryReadTChanRW (TChanRW c) = TChan.tryReadTChan c+++peekTChanRW :: Readable scope => TChanRW scope a -> STM a+peekTChanRW (TChanRW c) = TChan.peekTChan c+++tryPeekTChanRW :: Readable scope => TChanRW scope a -> STM (Maybe a)+tryPeekTChanRW (TChanRW c) = TChan.tryPeekTChan c+++newBroadcastTChanRW :: STM (TChanRW 'Write a)+newBroadcastTChanRW = TChanRW <$> TChan.newBroadcastTChan+++dupTChanRW :: Writable scopeIn+           => Readable scopeOut+           => TChanRW scopeIn a -> STM (TChanRW scopeOut a)+dupTChanRW (TChanRW c) = TChanRW <$> TChan.dupTChan c+++cloneTChanRW :: Writable scopeIn+           => Readable scopeOut+           => TChanRW scopeIn a -> STM (TChanRW scopeOut a)+cloneTChanRW (TChanRW c) = TChanRW <$> TChan.cloneTChan c
+ src/Control/Concurrent/STM/TChan/Typed/Extra.hs view
@@ -0,0 +1,104 @@+{-# LANGUAGE+    DataKinds+  #-}++module Control.Concurrent.STM.TChan.Typed.Extra where++import Control.Monad (forever)+import Control.Concurrent (threadDelay)+import Control.Concurrent.STM (STM, atomically)+-- import Control.Concurrent.STM.TVar (newTVar, readIORef, writeIORef)+import Control.Concurrent.STM.TMVar (newEmptyTMVar, tryTakeTMVar, putTMVar)+import Control.Concurrent.Chan.Scope (Scope (..))+import Control.Concurrent.STM.TChan.Typed (TChanRW, readTChanRW, writeTChanRW, newTChanRW, allowWriting, writeOnly)+import Control.Concurrent.Async (Async, async, cancel, wait)++++type DiffNanosec = Int+++-- | Note: In this model, even though we are using STM, a write to the+-- outgoing channel does not imply a transactional write to the output+-- channel; they are separated between a run IO layer, which means+-- we cannot atomically debounce or interleave the system (because+-- that depends on real-world time).+debounceStatic :: DiffNanosec -> TChanRW 'Read a -> IO (TChanRW 'Write a, Async ())+debounceStatic toWaitFurther outputChan = do+  (presentedChan,writingThread) <- atomically $ (,)+                                             <$> newTChanRW+                                             <*> newEmptyTMVar++  let invokeWrite x = do+        threadDelay toWaitFurther+        atomically $ writeTChanRW (allowWriting outputChan) x++  writer <- async $ forever $ do+    x <- atomically $ readTChanRW presentedChan++    newWriter <- async (invokeWrite x)++    mInvoker <- atomically $ tryTakeTMVar writingThread+    case mInvoker of+      Nothing -> pure ()+      Just i -> cancel i+    atomically $ putTMVar writingThread newWriter++  pure (writeOnly presentedChan, writer)+++throttleStatic :: DiffNanosec -> TChanRW 'Read a -> IO (TChanRW 'Write a, Async ())+throttleStatic toWaitFurther outputChan = do+  (presentedChan,writingThread) <- atomically $ (,)+                                             <$> newTChanRW+                                             <*> newEmptyTMVar++  let invokeWrite x = do+        threadDelay toWaitFurther+        atomically $ writeTChanRW (allowWriting outputChan) x++  writer <- async $ forever $ do+    x <- atomically $ readTChanRW presentedChan++    mInvoker <- atomically $ tryTakeTMVar writingThread+    case mInvoker of+      Nothing -> pure ()+      Just i -> wait i+    newWriter <- async (invokeWrite x)+    atomically $ putTMVar writingThread newWriter++  pure (writeOnly presentedChan, writer)+++intersperseStatic :: DiffNanosec -> IO a -> TChanRW 'Read a -> IO (TChanRW 'Write a, Async (), Async ())+intersperseStatic timeBetween xM outputChan = do+  (presentedChan,writingThread) <- atomically $ (,)+                                             <$> newTChanRW+                                             <*> newEmptyTMVar++  let invokeWritePing = do+        threadDelay timeBetween+        x <- xM+        atomically $ writeTChanRW (allowWriting outputChan) x++  writer <- async $ forever $ do+    mInvoker <- atomically $ tryTakeTMVar writingThread+    case mInvoker of+      Nothing -> pure ()+      Just i -> wait i+    newWriter <- async invokeWritePing+    atomically $ putTMVar writingThread newWriter++  listener <- async $ forever $ do+    (y,mInvoker) <- atomically $ do+      y' <- readTChanRW presentedChan++      (\q -> (y',q)) <$> tryTakeTMVar writingThread++    case mInvoker of+      Nothing -> pure ()+      Just i -> cancel i++    atomically $ writeTChanRW (allowWriting outputChan) y++  pure (writeOnly presentedChan, writer, listener)