packages feed

box (empty) → 0.0.1.1

raw patch · 22 files changed

+2286/−0 lines, 22 filesdep +asyncdep +attoparsecdep +basesetup-changed

Dependencies added: async, attoparsec, base, box, concurrency, contravariant, data-default, dejafu, doctest, exceptions, flow, foldl, generic-lens, lens, mmorph, pipes, profunctors, protolude, random, stm, streaming, text, time, transformers, transformers-base

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Tony Day (c) 2018++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Tony Day nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ box.cabal view
@@ -0,0 +1,146 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.31.1.+--+-- see: https://github.com/sol/hpack+--+-- hash: 1e95b5568e907743dae547d71a55b1b3cd4d5845c032f31a2e81464d8a14b5b0++name:           box+version:        0.0.1.1+synopsis:       boxes+description:    concurrent, effectful boxes+category:       project+homepage:       https://github.com/tonyday567/box#readme+bug-reports:    https://github.com/tonyday567/box/issues+author:         Tony Day+maintainer:     tonyday567@gmail.com+copyright:      Tony Day (c) 2017+license:        BSD3+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    stack.yaml+    readme.md++source-repository head+  type: git+  location: https://github.com/tonyday567/box++library+  exposed-modules:+      Box+      Box.Box+      Box.Broadcast+      Box.Committer+      Box.Connectors+      Box.Cont+      Box.Control+      Box.Emitter+      Box.IO+      Box.Plugs+      Box.Queue+      Box.Stream+      Box.Time+      Box.Transducer+      Box.Updater+  other-modules:+      Paths_box+  hs-source-dirs:+      src+  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -funbox-strict-fields+  build-depends:+      async+    , attoparsec+    , base >=4.7 && <5+    , concurrency+    , contravariant+    , data-default+    , dejafu+    , exceptions+    , flow+    , foldl+    , generic-lens+    , lens+    , mmorph+    , pipes+    , profunctors+    , protolude+    , random+    , stm+    , streaming+    , text+    , time+    , transformers+    , transformers-base+  default-language: Haskell2010++executable box-test+  main-is: ctest.hs+  other-modules:+      Paths_box+  hs-source-dirs:+      test+  ghc-options: -funbox-strict-fields -fforce-recomp -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      async+    , attoparsec+    , base >=4.7 && <5+    , box+    , concurrency+    , contravariant+    , data-default+    , dejafu+    , exceptions+    , flow+    , foldl+    , generic-lens+    , lens+    , mmorph+    , pipes+    , profunctors+    , protolude+    , random+    , stm+    , streaming+    , text+    , time+    , transformers+    , transformers-base+  default-language: Haskell2010++test-suite test+  type: exitcode-stdio-1.0+  main-is: test.hs+  other-modules:+      Paths_box+  hs-source-dirs:+      test+  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints+  build-depends:+      async+    , attoparsec+    , base >=4.7 && <5+    , box+    , concurrency+    , contravariant+    , data-default+    , dejafu+    , doctest+    , exceptions+    , flow+    , foldl+    , generic-lens+    , lens+    , mmorph+    , pipes+    , profunctors+    , protolude+    , random+    , stm+    , streaming+    , text+    , time+    , transformers+    , transformers-base+  default-language: Haskell2010
+ readme.md view
@@ -0,0 +1,13 @@+[box](https://tonyday567.github.io/box/index.html) [![Build Status](https://travis-ci.org/tonyday567/box.svg)](https://travis-ci.org/tonyday567/box)+===++A concurrent, effectful, composable box to put stuff in and take stuff out.++compilation recipe+---++`box-test` takes about 5 minutes to run.++```+stack build --test --exec "$(stack path --local-install-root)/bin/box-test" --file-watch+```
+ src/Box.hs view
@@ -0,0 +1,149 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wall #-}++-- | Boxes that `emit`, `transduce` & `commit`+--+-- This library follows the ideas and code from [pipes-concurrency](https://hackage.haskell.org/package/pipes-concurrency) and [mvc](https://hackage.haskell.org/package/mvc) but with some polymorphic tweaks and definitively more pretentious names.+--+--+module Box+  ( -- $setup+    -- $commit+    -- $emit+    -- $transduce+    module Box.Box+  , module Box.Committer+  , module Box.Connectors+  , module Box.Cont+  , module Box.Emitter+  , module Box.IO+  , module Box.Plugs+  , module Box.Queue+  , module Box.Stream+  , module Box.Time+  , module Box.Transducer+  ) where++import Box.Box+import Box.Committer+import Box.Connectors+import Box.Cont+import Box.Emitter+import Box.IO+import Box.Plugs+import Box.Queue+import Box.Stream+import Box.Time+import Box.Transducer++-- $setup+-- >>> :set -XOverloadedStrings+-- >>> :set -XGADTs+-- >>> import Protolude+-- >>> import Box+-- >>> import qualified Streaming.Prelude as S+-- >>> import Control.Monad.Conc.Class as C+-- >>> let committer' = cStdout 100+-- >>> let emitter' = toEmit (S.each ["hi","bye","q","x"])+-- >>> let box' = Box <$> committer' <*> emitter'+-- >>> let transducer' = Transducer $ \s -> s & S.takeWhile (/="q") & S.map ("echo: " <>)++-- $commit+-- committing+--+-- >>> with (cStdout 100) $ \c -> C.atomically (commit c "something")+-- something+-- True+--+-- The monoid instance sends each commit to both mappended committers. Delaying effects are introduced to these examples to keep stdout clear of race effects.+--+-- turned off in doctest until the muddled upedness is sorted...+-- > let cDelay = cmap (\b -> sleep 0.1 >> pure (Just b)) <$> (liftC <$> cStdout 100)+-- > let cImmediate = liftC <$> cStdout 100+-- > (etcM () transducer' $ (Box <$> (cImmediate <> cDelay) <*> (liftE <$> emitter'))) >> sleep 1+-- echo: hi+-- echo: hi+-- echo: bye+-- echo: bye+--+-- >>> let c = fmap liftC $ cStdout 10+-- >>> let e = fmap liftE $ toEmit (S.each ["hi","bye","q","x"])+-- >>> let c' = cmap (\a -> if a=="q" then (sleep 1 >> putStrLn "stolen!" >> sleep 1 >> pure (Nothing)) else (pure (Just a))) <$> c :: Cont IO (Committer IO Text)+-- >>> fuse (pure . pure) $ Box <$> c' <*> e+-- hi+-- bye+-- stolen!+-- x+--+-- prism handler+--+-- >>> import Control.Lens (_Right)+-- >>> let e2 = (fmap (\x -> Right (x <> "_right")) <$> e) <> (fmap (\x -> Left (x <> "_left")) <$> e)+-- >>> let cright = handles _Right <$> c+-- >>> fuse (pure . pure) $ Box <$> cright <*> e2+-- hi_right+-- bye_right+-- q_right+-- x_right+--++-- | splitCommit+-- FIXME:+-- > cs <- splitCommit (cStdout 100)+-- > let c2 = contCommit <$> cs+-- > (etcM () transducer' $ (Box <$> c2 <*> (liftE <$> emitter'))) >> sleep 1+--++-- $emit+--+-- >>> with (S.each [0..] & toEmit) (C.atomically . emit) >>= print+-- Just 0+--+-- >>> let c = fmap liftC $ cStdout 10+-- >>> let e = fmap liftE $ toEmit (S.each ["hi","bye","q","x"])+-- >>> let e' = emap (\a -> if a=="q" then (sleep 0.1 >> putStrLn "stole a q!" >> sleep 0.1 >> pure (Nothing)) else (pure (Just a))) <$> e :: Cont IO (Emitter IO Text)+-- >>> fuse (pure . pure) $ Box <$> c <*> e'+-- hi+-- bye+-- stole a q!+-- x+--+-- >>> let e1 = fmap show <$> (toEmit $ delayTimed (S.each (zip (fromIntegral <$> [1..10]) ['a'..]))) :: Cont IO (Emitter (C.STM IO) Text)+-- >>> let e2 = fmap show <$> (toEmit $ delayTimed (S.each (zip ((\x -> fromIntegral x + 0.1) <$> [1..10]) (reverse ['a'..'z'])))) :: Cont IO (Emitter (C.STM IO) Text)+-- >>> let e12 = e1 <> e2+-- >>> etc () (Transducer identity) $ Box <$> cStdout 6 <*> emerge ((,) <$> e1 <*> e2)+-- 'a'+-- 'z'+-- 'b'+-- 'y'+-- 'c'+-- 'x'+--+-- >>> etc () (Transducer identity) $ Box <$> cStdout 6 <*> (liftA2 (<>) e1 e2)+-- 'a'+-- 'z'+-- 'b'+-- 'y'+-- 'c'+-- 'x'+--++-- $transduce+--+-- >>> etc () transducer' box'+-- echo: hi+-- echo: bye+--++-- | broadcasting+--+-- > (bcast, bcom) <- C.atomically broadcast+-- > (funn, fem) <- C.atomically funnel+-- >+
+ src/Box/Box.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -fno-warn-type-defaults #-}++-- | A box is something that commits and emits+--+module Box.Box+  ( Box(..)+  , liftB+  , bmap+  ) where++import Control.Lens hiding ((:>), (.>), (<|), (|>))+import Data.Semigroup hiding (First, getFirst)+import Box.Committer+import Box.Emitter+import Control.Monad.Conc.Class+import Protolude hiding ((.), (<>), STM)++-- | A Box is a product of a Committer m and an Emitter. Think of a box with an incoming wire and an outgoing wire. Now notice that the abstraction is reversable: are you looking at two wires from "inside a box"; a blind erlang grunt communicating with the outside world via the two thin wires, or are you looking from "outside the box"; interacting with a black box object. Either way, it's a box.+-- And either way, the committer is contravariant and the emitter covariant so it forms a profunctor.+--+-- a Box can also be seen as having an input tape and output tape, thus available for turing and finite-state machine metaphorics.+--+data Box m c e = Box+  { committer :: Committer m c+  , emitter :: Emitter m e+  }++instance (Functor m) => Profunctor (Box m) where+  dimap f g (Box c e) = Box (contramap f c) (fmap g e)++instance (Alternative m, Monad m) => Semigroup (Box m c e) where+  (<>) (Box c e) (Box c' e') = Box (c <> c') (e <> e')++instance (Alternative m, Monad m) => Monoid (Box m c e) where+  mempty = Box mempty mempty+  mappend = (<>)++-- | lift a box from STM+liftB :: (MonadConc m) => Box (STM m) a b -> Box m a b+liftB (Box c e) = Box (liftC c) (liftE e)++-- | a profunctor dimapMaybe+bmap :: (Monad m) => (a' -> m (Maybe a)) -> (b -> m (Maybe b')) -> Box m a b -> Box m a' b'+bmap fc fe (Box c e) = Box (cmap fc c) (emap fe e)
+ src/Box/Broadcast.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wall #-}++module Box.Broadcast+  ( Broadcaster(..)+  , broadcast+  , subscribe+  , Funneler(..)+  , funnel+  , widen+  ) where++import Box.Committer+import Box.Cont+import Box.Emitter+import Box.Queue+import Protolude hiding (STM, atomically, (.), (<>))+import Control.Concurrent.Classy.STM as C+import Control.Monad.Conc.Class as C++-- | a broadcaster +newtype Broadcaster m a = Broadcaster+  { unBroadcast :: TVar m (Committer m a)+  }++-- | create a (broadcaster, committer)+broadcast :: (MonadSTM stm) => stm (Broadcaster stm a, Committer stm a)+broadcast = do+  ref <- newTVar mempty+  let com = Committer $ \a -> do+        c <- readTVar ref+        commit c a+  return (Broadcaster ref, com)++-- | subscribe to a broadcaster+subscribe :: (MonadConc m) => Broadcaster (STM m) a -> Cont m (Emitter (STM m) a)+subscribe (Broadcaster tvar) = Cont $ \e -> queueE cio e+  where+    cio c = atomically $ modifyTVar' tvar (mappend c)++-- | a funneler+newtype Funneler m a = Funneler+  { unFunnel :: TVar m (Emitter m a)+  }++-- | create a (funneler, emitter)+funnel :: (MonadSTM stm) => stm (Funneler stm a, Emitter stm a)+funnel = do+  ref <- newTVar mempty+  let em =+        Emitter $ do+          e <- readTVar ref+          emit e+  pure (Funneler ref, em)++-- | widen to a funneler+widen :: (MonadConc m) => Funneler (STM m) a -> Cont m (Committer (STM m) a)+widen (Funneler tvar) =+  Cont $ \c -> queueC c $ \e -> atomically $ modifyTVar' tvar (mappend e)
+ src/Box/Committer.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wall #-}++-- | `commit`+module Box.Committer+  ( Committer(..)+  , liftC+  , cmap+  , handles+  ) where++import Control.Category+import Control.Lens hiding ((:>), (.>), (<|), (|>))+import Data.Functor.Constant+import Data.Functor.Contravariant.Divisible+import Data.Semigroup hiding (First, getFirst)+import Protolude hiding ((.), (<>), STM, atomically)+import Control.Monad.Conc.Class as C++-- | a Committer a "commits" values of type a. A Sink and a Consumer are some other metaphors for this.+--+-- A Committer 'absorbs' the value being committed; the value disappears into the opaque thing that is a Committer from the pov of usage.+--+newtype Committer m a = Committer+  { commit :: a -> m Bool+  }++instance (Applicative m) => Semigroup (Committer m a) where+  (<>) i1 i2 = Committer (\a -> (||) <$> commit i1 a <*> commit i2 a)++instance (Applicative m) => Monoid (Committer m a) where+  mempty = Committer (\_ -> pure False)+  mappend = (<>)++instance Contravariant (Committer m) where+  contramap f (Committer a) = Committer (a . f)++instance (Applicative m) => Divisible (Committer m) where+  conquer = Committer (\_ -> pure False)+  divide f i1 i2 =+    Committer $ \a ->+      case f a of+        (b, c) -> (||) <$> commit i1 b <*> commit i2 c++instance (Applicative m) => Decidable (Committer m) where+  lose f = Committer (absurd . f)+  choose f i1 i2 =+    Committer $ \a ->+      case f a of+        Left b -> commit i1 b+        Right c -> commit i2 c++-- | lift a committer from STM+liftC :: (MonadConc m) => Committer (STM m) a -> Committer m a+liftC c = Committer $ atomically . commit c++-- | This is a contramapMaybe, if such a thing existed, as the contravariant version of a `mapMaybe`.  See [witherable](https://hackage.haskell.org/package/witherable)+--+cmap :: (Monad m) => (b -> m (Maybe a)) -> Committer m a -> Committer m b+cmap f c = Committer go+  where+    go b = do+      fb <- f b+      case fb of+        Nothing -> pure True+        Just fb' -> commit c fb'++-- | prism handler+handles ::+     (Monad m)+  => ((b -> Constant (First b) b) -> (a -> Constant (First b) a))+    -- ^+  -> Committer m b+    -- ^+  -> Committer m a+handles k (Committer commit_) =+  Committer+    (\a ->+       case match a of+         Nothing -> return False+         Just b -> commit_ b)+  where+    match = getFirst . getConstant . k (Constant . First . Just)
+ src/Box/Connectors.hs view
@@ -0,0 +1,172 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -fno-warn-type-defaults #-}++-- | various ways to connect things up+module Box.Connectors+  ( fuse_+  , fuseSTM_+  , fuse+  , fuseSTM+  , forkEmit+  , feedback+  , feedbackE+  , fuseEmit+  , fuseEmitM+  , fuseCommit+  , emerge+  , emergeM+  , splitCommit+  , contCommit+  ) where++import Control.Category+import Control.Lens hiding ((:>), (.>), (<|), (|>))+import Data.Semigroup hiding (First, getFirst)+import Box.Box+import Box.Queue+import Box.Committer+import Box.Cont+import Box.Emitter+import Protolude hiding (STM, (.), (<>))+import Control.Monad.Conc.Class as C+import Control.Concurrent.Classy.Async as C++-- * primitives+-- | fuse an emitter directly to a committer+fuse_ :: (Monad m) => Emitter m a -> Committer m a -> m ()+fuse_ e c = go+  where+    go = do+      a <- emit e+      c' <- maybe (pure False) (commit c) a+      when c' go++-- | slightly more efficient version+fuseSTM_ :: (MonadConc m) => Emitter (STM m) a -> Committer (STM m) a -> m ()+fuseSTM_ e c = go+  where+    go = do+      b <-+        C.atomically $ do+          a <- emit e+          maybe (pure False) (commit c) a+      when b go++-- | fuse a box+--+-- > (fuse (pure . Just) $ liftB <$> (Box <$> cStdout 2 <*> emitter')) >> sleep 1+-- hi+-- bye+--+-- > etc () (Transducer id) == fuse (pure . pure) . fmap liftB+--+fuse :: (Monad m) => (a -> m (Maybe b)) -> Cont m (Box m b a) -> m ()+fuse f box = with box $ \(Box c e) -> fuse_ (emap f e) c++-- | fuse a box with an STM mapMaybe action+fuseSTM :: (MonadConc m) => (a -> (STM m) (Maybe b)) -> Cont m (Box (STM m) b a) -> m ()+fuseSTM f box = with box $ \(Box c e) -> fuseSTM_ (emap f e) c++-- | fuse-branch an emitter+forkEmit :: (Monad m) => Emitter m a -> Committer m a -> Emitter m a+forkEmit e c =+  Emitter $ do+    a <- emit e+    maybe (pure ()) (void <$> commit c) a+    pure a++-- * buffer hookups+-- | fuse a committer to a buffer+fuseCommit :: (MonadConc m) => Committer (STM m) a -> Cont m (Committer (STM m) a)+fuseCommit c = Cont $ \caction -> queueC caction (`fuseSTM_` c)++-- | fuse an emitter to a buffer+fuseEmit :: (MonadConc m) => Emitter (STM m) a -> Cont m (Emitter (STM m) a)+fuseEmit e = Cont $ \eaction -> queueE (fuseSTM_ e) eaction++-- | fuse an emitter to a buffer+fuseEmitM :: (MonadConc m) => Emitter m a -> Cont m (Emitter m a)+fuseEmitM e = Cont $ \eaction -> queueEM (fuse_ e) eaction+++-- | merge two emitters+--+-- This differs from `liftA2 (<>)` in that the monoidal (and alternative) instance of an Emitter is left-biased (The left emitter exhausts before the right one is begun). This merge is concurrent.+--+emerge ::+  (MonadConc m) =>+  Cont m (Emitter (STM m) a, Emitter (STM m) a) ->+  Cont m (Emitter (STM m) a)+emerge e =+  Cont $ \eaction ->+    with e $ \e' ->+      fst <$>+      C.concurrently+        (queueE (fuseSTM_ (fst e')) eaction)+        (queueE (fuseSTM_ (snd e')) eaction)++-- | monadic version+--+emergeM ::+  (MonadConc m) =>+  Cont m (Emitter m a, Emitter m a) ->+  Cont m (Emitter m a)+emergeM e =+  Cont $ \eaction ->+    with e $ \e' ->+      fst <$>+      C.concurrently+        (queueEM (fuse_ (fst e')) eaction)+        (queueEM (fuse_ (snd e')) eaction)++-- | merge two committers+--+-- not working+--+splitCommit :: (MonadConc m) =>+     Cont m (Committer (STM m) a)+  -> Cont m (Either (Committer (STM m) a) (Committer (STM m) a))+splitCommit c =+  Cont $ \kk ->+    with c $ \c' ->+      fst <$>+      C.concurrently+        (queueC (kk . Left) (`fuseSTM_` c'))+        (queueC (kk . Right) (`fuseSTM_` c'))++-- | a failed attempt to understand the either continuation style+contCommit :: Either (Committer m Text) (Committer m Text) -> Committer m Text+contCommit ec =+  Committer $ \a ->+    case ec of+      Left lc -> commit (contramap ("left " <>) lc) a+      Right rc -> commit rc a++-- | a box modifier that feeds commits back to the emitter+feedback ::+  (MonadConc m) =>+  (a -> m (Maybe b)) ->+  Cont m (Box m b a) ->+  Cont m (Box m b a)+feedback f box =+  Cont $ \bio ->+    with box $ \(Box c e) -> do+      fuse_ (emap f e) c+      bio (Box c e)++-- | an emitter post-processor that cons transformed emissions back into the emitter+feedbackE ::+  (MonadConc m) =>+  (a -> m (Maybe a)) ->+  Emitter m a ->+  Cont m (Emitter m a)+feedbackE f e =+  emergeM ((,) <$> pure e <*> fuseEmitM (emap f e))
+ src/Box/Cont.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}+{-# OPTIONS_GHC -Wall #-}++module Box.Cont+  ( Cont(..)+  , Cont_(..)+  ) where++import Control.Applicative+import Control.Monad.IO.Class (MonadIO(liftIO))+import Data.Monoid (Monoid(..))+import Data.Semigroup (Semigroup(..))++-- | A continuation similar to `ContT` but where the result type is swallowed by an existential+newtype Cont m a = Cont+  { with :: forall r. (a -> m r) -> m r+  }++instance Functor (Cont m) where+  fmap f mx = Cont (\return_ -> mx `with` \x -> return_ (f x))++instance Applicative (Cont m) where+  pure r = Cont (\return_ -> return_ r)+  mf <*> mx = Cont (\return_ -> mf `with` \f -> mx `with` \x -> return_ (f x))++instance Monad (Cont m) where+  return r = Cont (\return_ -> return_ r)+  ma >>= f = Cont (\return_ -> ma `with` \a -> f a `with` \b -> return_ b)++instance (MonadIO m) => MonadIO (Cont m) where+  liftIO m =+    Cont+      (\return_ -> do+         a <- liftIO m+         return_ a)++instance (Semigroup a) => Semigroup (Cont m a) where+  (<>) = liftA2 (<>)++instance (Functor m, Semigroup a, Monoid a) => Monoid (Cont m a) where+  mempty = pure mempty+  mappend = (<>)++-- | sometimes you have no choice but to void it up+newtype Cont_ m a = Cont_+  { with_ :: (a -> m ()) -> m ()+  }++instance Functor (Cont_ m) where+  fmap f mx = Cont_ (\return_ -> mx `with_` \x -> return_ (f x))++instance Applicative (Cont_ m) where+  pure r = Cont_ (\return_ -> return_ r)+  mf <*> mx = Cont_ (\return_ -> mf `with_` \f -> mx `with_` \x -> return_ (f x))++instance Monad (Cont_ m) where+  return r = Cont_ (\return_ -> return_ r)+  ma >>= f = Cont_ (\return_ -> ma `with_` \a -> f a `with_` \b -> return_ b)++instance (MonadIO m) => MonadIO (Cont_ m) where+  liftIO m =+    Cont_+      (\return_ -> do+         a <- liftIO m+         return_ a)++instance (Semigroup a) => Semigroup (Cont_ m a) where+  (<>) = liftA2 (<>)++instance (Functor m, Semigroup a, Monoid a) => Monoid (Cont_ m a) where+  mempty = pure mempty+  mappend = (<>)
+ src/Box/Control.hs view
@@ -0,0 +1,154 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# LANGUAGE RankNTypes #-}+{-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -Wno-redundant-constraints #-}++module Box.Control+  ( ControlComm(..)+  , ControlBox+  , ControlConfig(..)+  , consoleControlBox+  , parseControlComms+  , controlBox+  , runControlBox+  , testBox+  , timeOut+  ) where++import Box+import Control.Applicative+import Control.Category+import Control.Concurrent.Async+import Control.Lens hiding ((|>))+import Control.Monad+import Data.Data+import Data.Default+-- import Data.IORef+import Flow+import GHC.Generics+import Protolude hiding ((.), STM)+import Text.Read (readMaybe)+import qualified Data.Attoparsec.Text as A+import qualified Data.Text as Text+import qualified Streaming.Prelude as S+import Control.Monad.Conc.Class as C++data ControlComm+  = Ready -- ready for comms+  | Check -- check for existence+  | Died -- died (of its own accord)+  | Stop -- stop (without shutting down)+  | Kill -- stop and quit (& cancel thread)+  | ShutDown -- successfully Killed+  | Start -- start (if not yet started)+  | Reset -- stop and start (potentially cancelling a previous instance)+  | On Bool -- are we live?+  | Log Text+  deriving (Show, Read, Eq, Data, Typeable, Generic)++type ControlBox m = (MonadConc m) => Cont m (Box (STM m) ControlComm ControlComm)++data ControlConfig+  = KeepAlive Double+  | AllowDeath+  deriving (Show, Eq)++instance Default ControlConfig where+  def = AllowDeath++consoleControlBox :: ControlBox IO+consoleControlBox =+  Box <$>+  (contramap show <$>+   (cStdout 1000 :: Cont IO (Committer (STM IO) Text))) <*>+  (emap (pure . either (const Nothing) Just) <$>+   (eParse parseControlComms <$>+    eStdin 1000))++parseControlComms :: A.Parser ControlComm+parseControlComms =+  A.string "q" *> return Stop <|> A.string "s" *> return Start <|>+  A.string "x" *> return Kill <|> do+    res <- readMaybe . Text.unpack <$> A.takeText+    case res of+      Nothing -> mzero+      Just a -> return a++-- | an effect that can be started and stopped+-- committer is an existence test+-- controlBox :: (MonadConc m) => ControlConfig -> m () -> ControlBox m+controlBox+  :: ControlConfig+     -> IO a -> Box (STM IO) ControlComm ControlComm -> IO Bool+controlBox cfg app (Box c e) = do+  ref' <- C.newIORef Nothing+  go ref'+  where+    go ref = do+      msg <- C.atomically $ emit e+      case msg of+        Nothing -> go ref+        Just msg' ->+          case msg' of+            Check -> do+              a <- C.readIORef ref+              _ <-+                C.atomically $ commit c $ On (bool True False (isNothing a))+              go ref+            Start -> do+              a <- C.readIORef ref+              when (isNothing a) (void $ start ref c)+              go ref+            Stop -> cancel' ref >> go ref+            Kill -> cancel' ref >> C.atomically (commit c ShutDown)+            Died ->+              case cfg of+                AllowDeath -> C.atomically $ commit c ShutDown+                KeepAlive x -> do+                  sleep x+                  _ <- C.atomically $ commit c Start+                  go ref+            Reset -> do+              a <- C.readIORef ref+              when (not $ isNothing a) (cancel' ref)+              _ <- start ref c+              go ref+            _ -> go ref+    start ref c' = do+      a' <- async (app >> C.atomically (commit c' Died))+      C.writeIORef ref (Just a')+      C.atomically $ commit c' Ready+    cancel' ref = do+      mapM_ cancel =<< C.readIORef ref+      C.writeIORef ref Nothing++runControlBox :: ControlConfig -> IO () -> IO ()+runControlBox cfg action =+  etc+    ()+    (Transducer $ \s -> s & S.takeWhile (/= ShutDown))+    (boxForgetPlug (void <$> controlBox cfg action))++-- | send Start, wait for a Ready signal, run action, wait x secs, then send Quit+testBox :: IO Bool+testBox = cb+  where+    action =+      sequence_ $+      (\x -> putStrLn x >> sleep 1) . (show :: Integer -> Text) <$>+      reverse [0 .. 10]+    cb = with consoleControlBox (controlBox (KeepAlive 3) action)+  -- buff (bounded 1)+  -- ControlStart+  -- ControlReady+  -- ControlQuit++timeOut :: Double -> ControlBox m+timeOut t =+  Box <$> mempty <*> ((lift (sleep t) >> S.yield Stop) |> toEmit)
+ src/Box/Emitter.hs view
@@ -0,0 +1,126 @@+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -fno-warn-type-defaults #-}++-- | `emit`+module Box.Emitter+  ( Emitter(..)+  , liftE+  , emap+  , keeps+  , eRead+  , eParse+  ) where++import Control.Category ((.))+import Data.Functor.Constant+import Data.Semigroup hiding (First, getFirst)+import Protolude hiding ((.), (<>), STM, atomically)+import qualified Data.Attoparsec.Text as A+import qualified Data.Text as Text+import Control.Monad.Conc.Class as C++-- | an `Emitter` "emits" values of type a. A Source & a Producer (of 'a's) are the two other alternative but overloaded metaphors out there.+--+-- An Emitter 'reaches into itself' for the value to emit, where itself is an opaque thing from the pov of usage.  An Emitter is named for its main action: it emits.+--+newtype Emitter m a = Emitter+  { emit :: m (Maybe a)+  }++instance (Functor m) => Functor (Emitter m) where+  fmap f m = Emitter (fmap (fmap f) (emit m))++instance (Applicative m) => Applicative (Emitter m) where+  pure r = Emitter (pure (pure r))+  mf <*> mx = Emitter ((<*>) <$> emit mf <*> emit mx)++instance (Monad m) => Monad (Emitter m) where+  return r = Emitter (return (return r))+  m >>= f =+    Emitter $ do+      ma <- emit m+      case ma of+        Nothing -> return Nothing+        Just a -> emit (f a)++instance (Monad m, Alternative m) => Alternative (Emitter m) where+  empty = Emitter (pure Nothing)+  x <|> y =+    Emitter $ do+      (i, ma) <- fmap ((,) y) (emit x) <|> fmap ((,) x) (emit y)+      case ma of+        Nothing -> emit i+        Just a -> pure (Just a)++instance (Alternative m, Monad m) => MonadPlus (Emitter m) where+  mzero = empty+  mplus = (<|>)++instance (Alternative m, Monad m) => Semigroup (Emitter m a) where+  (<>) = (<|>)++instance (Alternative m, Monad m) => Monoid (Emitter m a) where+  mempty = empty+  mappend = (<>)++liftE :: (MonadConc m) => Emitter (STM m) a -> Emitter m a+liftE = Emitter . atomically . emit++-- | like a monadic mapMaybe. (See [witherable](https://hackage.haskell.org/package/witherable))+--+emap :: (Monad m) => (a -> m (Maybe b)) -> Emitter m a -> Emitter m b+emap f e = Emitter go+  where+    go = do+      a <- emit e+      case a of+        Nothing -> pure Nothing+        Just a' -> do+          fa <- f a'+          case fa of+            Nothing -> go+            Just fa' -> pure (Just fa')++-- | prism handler+keeps ::+     (Monad m)+  => ((b -> Constant (First b) b) -> (a -> Constant (First b) a))+    -- ^+  -> Emitter m a+    -- ^+  -> Emitter m b+keeps k (Emitter emit_) = Emitter emit_'+  where+    emit_' = do+      ma <- emit_+      case ma of+        Nothing -> return Nothing+        Just a ->+          case match a of+            Nothing -> emit_'+            Just b -> return (Just b)+    match = getFirst . getConstant . k (Constant . First . Just)++-- | attoparsec parse emitter+eParse :: (Functor m) => A.Parser a -> Emitter m Text -> Emitter m (Either Text a)+eParse parser e = (either (Left . Text.pack) Right . A.parseOnly parser) <$> e++-- | read parse emitter+eRead ::+     (Functor m, Read a)+  => Emitter m Text+  -> Emitter m (Either Text a)+eRead = fmap $ parsed . Text.unpack+  where+    parsed str =+      case reads str of+        [(a, "")] -> Right a+        _ -> Left (Text.pack str)
+ src/Box/IO.hs view
@@ -0,0 +1,177 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -fno-warn-type-defaults #-}++-- | IO actions+module Box.IO+  ( cStdin_+  , cStdin+  , cStdin'+  , eStdin+  , readStdin+  , eStdout_+  , eStdout+  , eStdoutM+  , eStdout'+  , cStdout+  , showStdout+  , consolePlug+  , emitLines+  , commitLines+  , cCRef+  , cCRefM+  , toListM+  , getCommissions+  , getEmissions+  ) where++import Control.Category+import qualified Control.Foldl as L+import Control.Lens hiding ((:>), (.>), (<|), (|>))+import Data.Semigroup hiding (First, getFirst)+import qualified Data.Text.IO as Text+import Box.Box+import Box.Committer+import Box.Cont+import Box.Emitter+import Box.Plugs+import Box.Stream+import Box.Transducer+import Flow+import Protolude hiding ((.), (<>), STM)+import Streaming (Of(..), Stream)+import qualified Streaming.Internal as S+import qualified Streaming.Prelude as S+import qualified Control.Monad.Conc.Class as C+import qualified Control.Concurrent.Classy.IORef as C+import Control.Monad.Conc.Class (STM)++-- * console+-- | a single stdin committer action+cStdin_ :: Committer (STM IO) Text -> IO ()+cStdin_ c = do+  a <- getLine+  void $ atomically $ commit c a++-- | a finite stdin committer action+cStdin :: Int -> Committer (STM IO) Text -> IO ()+cStdin n c = replicateM_ n (cStdin_ c)++-- | a forever stdin committer action+cStdin' :: Committer (STM IO) Text -> IO ()+cStdin' = forever . cStdin_++-- | a Cont stdin emitter+eStdin :: Int -> Cont IO (Emitter (STM IO) Text)+eStdin n = cStdin n |> emitPlug++-- | read from console, throwing away read errors+readStdin :: Read a => Cont IO (Emitter (STM IO) a)+readStdin = emap (pure . either (const Nothing) Just) . eRead <$> eStdin 1000++-- | a single stdout emitter action+eStdout_ :: (Print a) => Emitter (STM IO) a -> IO ()+eStdout_ e = do+  a <- atomically $ emit e+  case a of+    Nothing -> pure ()+    Just a' -> putStrLn a'++-- | a single stdout emitter action+eStdoutM_ :: (Print a) => Emitter IO a -> IO ()+eStdoutM_ e = do+  a <- emit e+  case a of+    Nothing -> pure ()+    Just a' -> putStrLn a'++-- | a finite stdout emitter action+eStdout :: (Print a) => Int -> Emitter (STM IO) a -> IO ()+eStdout n = replicateM_ n . eStdout_++-- | a finite stdout emitter action+eStdoutM :: (Print a) => Int -> Emitter IO a -> IO ()+eStdoutM n = replicateM_ n . eStdoutM_++-- | a forever stdout emitter action+eStdout' :: (Print a) => Emitter (STM IO) a -> IO ()+eStdout' = forever . eStdout_++-- | a Cont stdout committer+cStdout :: Print a => Int -> Cont IO (Committer (STM IO) a)+cStdout n = eStdout n |> commitPlug++-- | show to stdout+showStdout :: Show a => Cont IO (Committer (STM IO) a)+showStdout = contramap show <$> (cStdout 1000 :: Cont IO (Committer (STM IO) Text))++-- | console box+-- > etc () (Trans $ \s -> s & S.takeWhile (/="q") & S.map ("echo: " <>)) (console 5)+consolePlug :: Int -> Cont IO (Box (STM IO) Text Text)+consolePlug n = boxPlug (eStdout n) (cStdin n)++-- * file operations+-- | emit lines from a file+emitLines :: FilePath -> Cont IO (Emitter (STM IO) Text)+emitLines filePath = Cont (withFile filePath ReadMode) >>= fromHandle .> toEmit+  where+    fromHandle :: Handle -> Stream (Of Text) IO ()+    fromHandle h =+      forever $ do+        a <- liftIO $ Text.hGetLine h+        S.yield a++-- | commit lines to a file+commitLines :: FilePath -> Cont IO (Committer (STM IO) Text)+commitLines filePath =+  Cont (withFile filePath WriteMode) >>= toHandle .> toCommit+  where+    toHandle h = loop+      where+        loop str =+          case str of+            S.Return r -> return r+            S.Effect m -> m >>= loop+            S.Step (s :> rest) -> do+              liftIO (Text.hPutStrLn h s)+              loop rest++-- * concurrent refs+-- | commit to a list CRef+cCRef :: (C.MonadConc m) => m (C.IORef m [b], Cont m (Committer (C.STM m) b), m [b])+cCRef = do+  ref <- C.newIORef []+  let c = toCommitFold $+        L.FoldM (\x a -> C.modifyIORef x (a :) >> pure x) (pure ref) (const $ pure ())+  let res = reverse <$> C.readIORef ref+  pure (ref, c, res)++-- | commit to a monoidal CRef+cCRefM :: (C.MonadConc m, Monoid a) => m (C.IORef m a, Cont m (Committer (C.STM m) a), m a)+cCRefM = do+  ref <- C.newIORef mempty+  let c = toCommitFold $+        L.FoldM (\x a -> C.modifyIORef x (a <>) >> pure x) (pure ref) (const $ pure ())+  let res = C.readIORef ref+  pure (ref, c, res)++-- | fold an emitter through a transduction, committing to a list+toListM :: (C.MonadConc m) => Cont m (Emitter (C.STM m) a) -> s -> Transducer s a b -> m ([b], s)+toListM e s t = do+  (_, c, res) <- cCRef+  r <- etc s t (Box <$> c <*> e)+  (,) <$> res <*> pure r++-- | get all commissions as a list+getCommissions :: (C.MonadConc m) => Cont m (Emitter (C.STM m) a) -> s -> Transducer s a b -> m [b]+getCommissions e s t = fst <$> toListM e s t++-- | get all emissions+getEmissions :: (C.MonadConc m) => Int -> Cont m (Emitter (C.STM m) a) -> m [a]+getEmissions n e = fst <$> toListM e () (Transducer $ S.take n)
+ src/Box/Plugs.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -fno-warn-type-defaults #-}++-- | plugs+-- box continuations+--+module Box.Plugs+  ( commitPlug+  , emitPlug+  , emitPlugM+  , boxPlug+  , boxForgetPlug+  ) where++import Control.Category+import Box.Committer+import Box.Cont+import Box.Box+import Box.Queue+import Box.Emitter+import GHC.Conc+import Protolude hiding ((.), (<>))++-- * plugs+-- | hook an emitter action to a queue, creating a committer continuation+commitPlug :: (Emitter STM a -> IO ()) -> Cont IO (Committer STM a)+commitPlug eio = Cont $ \cio -> queueC cio eio++-- | hook a committer action to a queue, creating an emitter continuation+emitPlug :: (Committer STM a -> IO r) -> Cont IO (Emitter STM a)+emitPlug cio = Cont $ \eio -> queueE cio eio++-- | hook a committer action to a queue, creating an emitter continuation+emitPlugM :: (Committer IO a -> IO r) -> Cont IO (Emitter IO a)+emitPlugM cio = Cont $ \eio -> queueEM cio eio++-- | create a double-queued box plug+boxPlug ::+     (Emitter STM a -> IO ())+  -> (Committer STM b -> IO ())+  -> Cont IO (Box STM a b)+boxPlug eio cio = Box <$> commitPlug eio <*> emitPlug cio++-- | create a box plug from a box action.  Caution: implicitly, this (has to) forget interactions between emitter and committer in the one action (and it does so silently).  These forgotten interactions are typically those that create races+boxForgetPlug :: (Box STM b a -> IO ()) -> Cont IO (Box STM a b)+boxForgetPlug bio = boxPlug (bio . Box mempty) (bio . (`Box` mempty))
+ src/Box/Queue.hs view
@@ -0,0 +1,262 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -fno-warn-type-defaults #-}+{-# LANGUAGE OverloadedStrings #-}++-- | queues+-- Follows [pipes-concurrency](https://hackage.haskell.org/package/pipes-concurrency)+--+module Box.Queue+  ( Queue(..)+  , queue+  , queueC+  , queueE+  , queueCM+  , queueEM+  , queueCLog+  , queueELog+  , waitCancel+  , toBoxLog+  , ends+  ) where++import Box.Box+import Box.Committer+import Box.Emitter+-- import GHC.Conc+import Protolude hiding (STM, check, wait, cancel, atomically, withAsync, concurrently)+import Control.Concurrent.Classy.STM as C+import Control.Monad.Conc.Class as C+import Control.Concurrent.Classy.Async as C+import Control.Monad.Catch as C++-- | 'Queue' specifies how messages are queued+data Queue a+  = Unbounded+  | Bounded Int+  | Single+  | Latest a+  | Newest Int+  | New++-- | create a queue, returning the ends+ends :: MonadSTM stm => Queue a -> stm (a -> stm (), stm a)+ends qu =+  case qu of+    Bounded n -> do+      q <- newTBQueue n+      return (writeTBQueue q, readTBQueue q)+    Unbounded -> do+      q <- newTQueue+      return (writeTQueue q, readTQueue q)+    Single -> do+      m <- newEmptyTMVar+      return (putTMVar m, takeTMVar m)+    Latest a -> do+      t <- newTVar a+      return (writeTVar t, readTVar t)+    New -> do+      m <- newEmptyTMVar+      return (\x -> tryTakeTMVar m *> putTMVar m x, takeTMVar m)+    Newest n -> do+      q <- newTBQueue n+      let write x = writeTBQueue q x <|> (tryReadTBQueue q *> write x)+      return (write, readTBQueue q)++-- | write to a queue, checking the seal+writeCheck :: (MonadSTM stm) => TVar stm Bool -> (a -> stm ()) -> a -> stm Bool+writeCheck sealed i a = do+  b <- readTVar sealed+  if b+    then pure False+    else do+      i a+      pure True++-- | read from a queue, and retry if not sealed+readCheck :: MonadSTM stm => TVar stm Bool -> stm a -> stm (Maybe a)+readCheck sealed o = (Just <$> o) <|> (do+  b <- readTVar sealed+  C.check b+  pure Nothing)++-- | turn a queue into a box (and a seal)+toBox :: (MonadSTM stm) =>+  Queue a -> stm (Box stm a a, stm ())+toBox q = do+  (i, o) <- ends q+  sealed <- newTVarN "sealed" False+  let seal = writeTVar sealed True+  pure (Box+        (Committer (writeCheck sealed i))+        (Emitter (readCheck sealed o)),+        seal)++-- | write to a queue, checking the seal+writeCheckLog :: (Show a, MonadIO m, MonadConc m) => TVar (STM m) Bool -> (a -> (STM m) ()) -> a -> m Bool+writeCheckLog sealed i a = do+  b <- atomically $ readTVar sealed+  if b+    then do+      putStrLn ("writeCheckLog sealed" :: Text)+      pure False+    else do+      putStrLn $ "writeCheckLog writing: " <> (show a:: Text)+      atomically $ i a+      pure True++-- | read from a queue, and retry if not sealed+readCheckLog :: (Show a, MonadIO m, MonadConc m) => TVar (STM m) Bool -> (STM m) a -> m (Maybe a)+readCheckLog sealed o = do+  o' <- atomically o+  putStrLn $ "readCheckLog reading" <> (show o' :: Text)+  atomically (Just <$> o <|> do+                            b <- readTVar sealed+                            C.check b+                            pure Nothing)++toBoxM :: (MonadConc m) =>+  Queue a -> m (Box m a a, m ())+toBoxM q = do+  (i, o) <- atomically $ ends q+  sealed <- atomically $ newTVarN "sealed" False+  let seal = atomically $ writeTVar sealed True+  pure (Box+        (Committer (atomically . writeCheck sealed i))+        (Emitter (atomically $ readCheck sealed o)),+        seal)++toBoxLog :: (Show a, MonadIO m, MonadConc m) =>+  Queue a -> m (Box m a a, m ())+toBoxLog q = do+  (i, o) <- atomically $ ends q+  sealed <- atomically $ newTVarN "sealed" False+  initSeal <- atomically $ readTVar sealed+  putStrLn $ "toBoxLog: seal: " <> (show initSeal :: Text)+  let seal = atomically $ writeTVar sealed True+  pure (Box+        (Committer (writeCheckLog sealed i))+        (Emitter (readCheckLog sealed o)),+        seal)++-- | wait for the first action, and then cancel the second+waitCancel :: (MonadConc m) => m b -> m a -> m b+waitCancel a b =+  withAsync a $ \a' ->+    withAsync b $ \b' -> do+      a'' <- wait a'+      cancel b'+      pure a''++-- | connect a committer and emitter action via spawning a queue, and wait for both to complete.+withQ :: (MonadConc m) =>+     Queue a+  -> (Queue a -> (STM m) (Box (STM m) a a, (STM m) ()))+  -> (Committer (STM m) a -> m l)+  -> (Emitter (STM m) a -> m r)+  -> m (l, r)+withQ q spawner cio eio =+  C.bracket+    (atomically $ spawner q)+    (\(_, seal) -> atomically seal)+    (\(box, seal) ->+       concurrently+         (cio (committer box) `C.finally` atomically seal)+         (eio (emitter box) `C.finally` atomically seal))++-- | connect a committer and emitter action via spawning a queue, and wait for both to complete.+withQM :: (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)+withQM q spawner cio eio =+  C.bracket+    (spawner q)+    (\(_, seal) -> seal)+    (\(box, seal) ->+       concurrently+         (cio (committer box) `C.finally` seal)+         (eio (emitter box) `C.finally` seal))++-- | connect a committer and emitter action via spawning a queue, and wait for both to complete.+withQMLog :: (MonadConc m, MonadIO m) =>+     Queue a+  -> (Queue a -> m (Box m a a, m ()))+  -> (Committer m a -> m l)+  -> (Emitter m a -> m r)+  -> m (l, r)+withQMLog q spawner cio eio =+  C.bracket+    (spawner q)+    (\(_, seal) -> seal)+    (\(box, seal) ->+       concurrently+         (cio (committer box) `C.finally` (putStrLn ("committer sealed" :: Text) >> seal))+         (eio (emitter box) `C.finally` (putStrLn ("emitter sealed" :: Text) >> seal)))++-- | create an unbounded queue+queue ::+  (MonadConc m) =>+  (Committer (STM m) a -> m l) ->+  (Emitter (STM m) a -> m r) ->+  m (l, r)+queue = withQ Unbounded toBox++-- | create an unbounded queue, returning the emitter result+queueE ::+  (MonadConc m) =>+  (Committer (STM m) a -> m l) ->+  (Emitter (STM m) a -> m r) ->+  m r+queueE cm em = snd <$> withQ Unbounded toBox cm em++-- | create an unbounded queue, returning the committer result+queueC ::+  (MonadConc m) =>+  (Committer (STM m) a -> m l) ->+  (Emitter (STM m) a -> m r) ->+  m l+queueC cm em = fst <$> withQ Unbounded toBox cm em+++-- | create an unbounded queue, returning the emitter result+queueCM ::+  (MonadConc m) =>+  (Committer m a -> m l) ->+  (Emitter m a -> m r) ->+  m l+queueCM cm em = fst <$> withQM Unbounded toBoxM cm em++-- | create an unbounded queue, returning the emitter result+queueEM ::+  (MonadConc m) =>+  (Committer m a -> m l) ->+  (Emitter m a -> m r) ->+  m r+queueEM cm em = snd <$> withQM Unbounded toBoxM cm em++-- | create an unbounded queue, returning the emitter result+queueELog ::+  (Show a, MonadConc m, MonadIO m) =>+  (Committer m a -> m l) ->+  (Emitter m a -> m r) ->+  m r+queueELog cm em = snd <$> withQMLog Unbounded toBoxLog+  (\c -> putStrLn ("cm acted" :: Text) >> cm c)+  (\c -> putStrLn ("em acted" :: Text) >> em c)++-- | create an unbounded queue, returning the committer result+queueCLog ::+  (Show a, MonadConc m, MonadIO m) =>+  (Committer m a -> m l) ->+  (Emitter m a -> m r) ->+  m l+queueCLog cm em = fst <$> withQM Unbounded toBoxLog cm em
+ src/Box/Stream.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -fno-warn-type-defaults #-}++-- | Streaming functionality+--++module Box.Stream+  ( toStream+  , fromStream+  , toCommit+  , toCommitFold+  , toCommitSink+  , toEmit+  , queueStream+  , toStreamM+  , fromStreamM+  ) where++import Control.Category++import Box.Committer+import Box.Cont+import Box.Emitter+import Box.Queue+-- import Box.Transducer+import Flow+import Streaming (Of(..), Stream)+import qualified Control.Foldl as L+import qualified Streaming.Prelude as S+import Control.Monad.Conc.Class as C+import Protolude hiding ((<>), (.), STM, check, wait, cancel, atomically, withAsync, concurrently)++-- * streaming+-- | create a committer from a stream consumer+toCommit :: (MonadConc m) => (Stream (Of a) m () -> m r) -> Cont m (Committer (STM m) a)+toCommit f =+  Cont (\c -> queueC c (\(Emitter o) -> f . toStream . Emitter $ o))++-- | create a committer from a fold+toCommitFold :: (MonadConc m) => L.FoldM m a () -> Cont m (Committer (STM m) a)+toCommitFold f = toCommit (fmap S.snd' . L.impurely S.foldM f)++-- | create a committer from a sink+toCommitSink :: (MonadConc m) => (a -> m ()) -> Cont m (Committer (STM m) a)+toCommitSink sink = toCommitFold (L.FoldM step begin done)+  where+    step x a = do+      sink a+      pure x+    begin = pure ()+    done = pure++-- | create an emitter from a stream+toEmit :: (MonadConc m) => Stream (Of a) m () -> Cont m (Emitter (STM m) a)+toEmit s = Cont (queueE (fromStream s))++-- | insert a queue into a stream (left biased collapse)+-- todo: look at biases+queueStream ::+     (MonadConc m) => Stream (Of a) m () -> Cont m (Stream (Of a) m ())+queueStream i = Cont $ \o -> queueE (fromStream i) (toStream .> o)++-- | turn an emitter into a stream+toStream :: (MonadConc m) => Emitter (STM m) a -> Stream (Of a) m ()+toStream e = toStreamM (liftE e)++-- | turn an emitter into a stream+toStreamM :: (MonadConc m) => Emitter m a -> Stream (Of a) m ()+toStreamM e = S.untilRight getNext+  where+    getNext = maybe (Right ()) Left <$> emit e++-- | turn a stream into a committer+fromStream :: (MonadConc m) => Stream (Of b) m () -> Committer (STM m) b -> m ()+fromStream s c = fromStreamM s (liftC c)++-- | turn a stream into a committer+fromStreamM :: (MonadConc m) => Stream (Of b) m () -> Committer m b -> m ()+fromStreamM s c = go s+  where+    go str = do+      eNxt <- S.next str -- uncons requires r ~ ()+      forM_ eNxt $ \(a, str') -> do+        continue <- commit c a+        when continue (go str')++
+ src/Box/Time.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# OPTIONS_GHC -fno-warn-type-defaults #-}++-- | timing effects+module Box.Time+  ( sleep+  , keepOpen+  , delayTimed+  , Stamped(..)+  , stampNow+  , emitStamp+  ) where++import Control.Applicative+import Control.Monad+import Data.Maybe+import Data.Time+import Box.Cont+import Box.Emitter+import Box.Stream+import Protolude hiding (threadDelay, STM)+import qualified Streaming.Prelude as S+import qualified Streaming as S+import Control.Monad.Conc.Class as C++-- | sleep for x seconds+sleep :: (MonadConc m) => Double -> m ()+sleep x = threadDelay (floor $ x * 1e6)++-- | keeping a box open sometimes needs a long running emitter+keepOpen :: (MonadConc m) => Cont m (Emitter (STM m) a)+keepOpen = toEmit $ lift $ threadDelay (365 * 24 * 60 * 60 * 10 ^ 6)++-- | a stream with suggested delays.  DiffTime is the length of time to wait since the start of the stream+-- > delayTimed (S.each (zip (fromIntegral <$> [1..10]) [1..10])) |> S.print+delayTimed :: (MonadConc m, MonadIO m) =>+     S.Stream (S.Of (NominalDiffTime, a)) m () -> S.Stream (S.Of a) m ()+delayTimed s = do+  t0 <- liftIO getCurrentTime+  go (S.hoistUnexposed lift s) t0+  where+    go s t0 = do+      n <- S.uncons s+      case n of+        Nothing -> pure ()+        Just ((t1, a'), s') -> do+          lift $ delayTo (addUTCTime t1 t0)+          S.yield a'+          go s' t0+    delayTo t = do+      now <- liftIO getCurrentTime+      let gap = max 0 $ diffUTCTime t now+      -- sleep gap+      threadDelay (truncate (gap * 1000000))++data Stamped a = Stamped+  { timestamp :: UTCTime+  , value :: a+  } deriving (Eq, Show, Read)++stampNow :: (MonadConc m, MonadIO m) => a -> m (Stamped a)+stampNow a = do+  t <- liftIO getCurrentTime+  pure $ Stamped t a++-- | adding a time stamp+-- todo: how to do this properly?+emitStamp ::+  (MonadConc m, MonadIO m) =>+  Cont m (Emitter m a) ->+  Cont m (Emitter m (Stamped a))+emitStamp e = emap (fmap Just . stampNow) <$> e++
+ src/Box/Transducer.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -fno-warn-type-defaults #-}++-- | `transduce`+--+module Box.Transducer+  ( Transducer(..)+  , etc+  , etcM+  , asPipe+  ) where++import Control.Category+import Control.Lens hiding ((:>), (.>), (<|), (|>))+import Control.Monad.Base (MonadBase, liftBase)+import Box.Box+import Box.Committer+import Box.Cont+import Box.Emitter+import Box.Stream+import Flow+import qualified Pipes+import qualified Pipes.Prelude as Pipes+import Protolude hiding ((.), (<>))+import Streaming (Of(..), Stream)+import qualified Streaming.Prelude as S+import Control.Monad.Conc.Class as C++-- | transduction+-- [wiki](https://en.wikipedia.org/wiki/Transducer) says: "A transducer is a device that converts energy from one form to another." Translated to context, this Transducer converts a stream of type a to a stream of a different type.+--+newtype Transducer s a b = Transducer+  { transduce :: forall m. Monad m =>+                             Stream (Of a) (StateT s m) () -> Stream (Of b) (StateT s m) ()+  }++instance Category (Transducer s) where+  (Transducer t1) . (Transducer t2) = Transducer (t1 . t2)+  id = Transducer id++-- | convert a Pipe to a Transducer+asPipe ::+     (Monad m)+  => Pipes.Pipe a b (StateT s m) ()+  -> (Stream (Of a) (StateT s m) () -> Stream (Of b) (StateT s m) ())+asPipe p s = ((s & Pipes.unfoldr S.next) Pipes.>-> p) & S.unfoldr Pipes.next++-- | emit - transduce - commit+--+-- with etc, you're in the box, and inside the box, there are no effects: just a stream of 'a's, pure functions and state tracking. It's a nice way to code, and very friendly for the compiler. When the committing and emitting is done, the box collapses to state.+--+-- The combination of an input tape, an output tape, and a state-based stream computation lends itself to the etc computation as a [finite-state transducer](https://en.wikipedia.org/wiki/Finite-state_transducer) or mealy machine.+--+etc :: (MonadConc m) => s -> Transducer s a b -> Cont m (Box (C.STM m) b a) -> m s+etc st t box =+  with box $ \(Box c e) ->+    (e |> toStream |> transduce t |> fromStream) c |> flip execStateT st++etcM :: (MonadConc m, MonadBase m m) => s -> Transducer s a b -> Cont m (Box m b a) -> m s+etcM st t box =+  with box $ \(Box c e) ->+    (liftE' e |> toStreamM |> transduce t |> fromStreamM) (liftC' c) |> flip execStateT st+  where+    liftC' c = Committer $ liftBase . commit c+    liftE' = Emitter . liftBase . emit+
+ src/Box/Updater.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE RankNTypes #-}+{-# OPTIONS_GHC -Wall #-}++-- | based on https://github.com/Gabriel439/Haskell-MVC-Updates-Library+module Box.Updater+  ( Updater(..)+  , updater+  , listen+  , updates+  ) where++import Control.Applicative (Applicative((<*>), pure))+import Control.Foldl (Fold(..), FoldM(..))+import qualified Control.Foldl as Foldl+import Data.IORef (newIORef, readIORef, writeIORef)+import Box+import Protolude++-- | An updater of a value a, where the updating process consists of an IO fold over an emitter+data Updater a =+  forall e. Updater (FoldM IO e a)+                    (Cont IO (Emitter STM e))++instance Functor Updater where+  fmap f (Updater fold' e) = Updater (fmap f fold') e++{-+> onLeft (f <*> x) = onLeft f <*> onLeft x+>+> onLeft (pure r) = pure r+-}+onLeft :: Monad m => FoldM m a b -> FoldM m (Either a x) b+onLeft (FoldM step begin done) = FoldM step' begin done+  where+    step' x (Left a) = step x a+    step' x _ = return x++{-+> onRight (f <*> x) = onRight f <*> onRight x+>+> onRight (pure r) = pure r+-}+onRight :: Monad m => FoldM m a b -> FoldM m (Either x a) b+onRight (FoldM step begin done) = FoldM step' begin done+  where+    step' x (Right a) = step x a+    step' x _ = return x++instance Applicative Updater where+  pure a = Updater (pure a) mempty+  (Updater foldL eL) <*> (Updater foldR eR) = Updater foldT eT+    where+      foldT = onLeft foldL <*> onRight foldR+      eT = fmap (fmap Left) eL <> fmap (fmap Right) eR++-- | Create an `Updatable` value using a pure `Fold`+updater :: Fold e a -> Cont IO (Emitter STM e) -> Updater a+updater fold' = Updater (Foldl.generalize fold')++-- | run an action on each update+-- > listen mempty = id+-- >+-- > listen (f <> g) = listen g . listen f+--+listen :: (a -> IO ()) -> Updater a -> Updater a+listen handler (Updater (FoldM step begin done) mController) =+  Updater (FoldM step' begin' done) mController+  where+    begin' = do+      x <- begin+      b <- done x+      handler b+      return x+    step' x a = do+      x' <- step x a+      b <- done x'+      handler b+      return x'++updates :: Updater a -> Cont IO (Emitter STM a)+updates (Updater (FoldM step begin done) e) = Cont $ \e' -> queueE cio e'+  where+    ioref c = do+      x <- begin+      a <- done x+      _ <- atomically $ commit c a+      newIORef x+    cio c =+      with e $ \e' -> do+        ioref' <- ioref c+        x <- readIORef ioref'+        e'' <- atomically $ emit e'+        case e'' of+          Nothing -> pure ()+          Just e''' -> do+            x' <- step x e'''+            a <- done x'+            _ <- atomically $ commit c a+            writeIORef ioref' x'
+ stack.yaml view
@@ -0,0 +1,6 @@+resolver: lts-13.21++packages:+  - .++extra-deps: []
+ test/ctest.hs view
@@ -0,0 +1,362 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE OverloadedLabels #-}+{-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -fno-warn-type-defaults #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeApplications #-}+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}++-- | dejavu testing+--+module Main where++import Control.Category (id)+import Control.Monad.Conc.Class as C+import Control.Concurrent.Classy.STM as C+import Box.Box+import Box.Committer+import Box.Emitter+import Box.Broadcast+import Box.Connectors+import Box.Cont+import Box.IO+import Box.Queue+import Box.Stream+import Box.Transducer+import Data.String+import Protolude hiding (STM)+import Test.DejaFu+import Test.DejaFu.Conc+import qualified Streaming.Prelude as S+import System.Random+import Control.Lens hiding ((:>), (.>), (<|), (|>))+import Control.Concurrent.Classy.Async as C+import qualified Control.Monad.Trans.State as Trans+import Data.Generics.Labels ()+import Data.Generics.Product++-- | the test box is a pure list emitter into an IORef appending list+testBox :: (MonadConc m) => Int -> m (Cont m (Box (STM m) Int Int), m [Int])+testBox n = do+  (_, c, res) <- cCRef+  let e = toEmit (S.take n $ S.each [0..])+  pure (Box <$> c <*> e, res)++-- | fuse+exFuse :: (MonadConc m) => Int -> m [Int]+exFuse n = do+  (b, res) <- testBox n+  fuse (pure . pure) (liftB <$> b)+  res++exFuseSTM :: (MonadConc m) => Int -> m [Int]+exFuseSTM n = do+  (b, res) <- testBox n+  fuseSTM (pure . pure) b+  res++exEtc :: (MonadConc m) => Int -> m [Int]+exEtc n = do+  (b, res) <- testBox n+  etc () (Transducer id) b+  res++-- | one emitter and 2 committers - STM fusion+e1c2 :: (MonadConc m) => Cont m (Emitter (STM m) b) -> m ([b],[b])+e1c2 e = do+  (_,c1,r1) <- cCRef+  (_,c2,r2) <- cCRef+  fuseSTM (pure . pure) (Box <$> c1 <*> e)+  fuseSTM (pure . pure) (Box <$> c2 <*> e)+  (,) <$> r1 <*> r2++exe1c2 :: (MonadConc m) => Int -> m ([Int],[Int])+exe1c2 n = e1c2 (toEmit (S.take n $ S.each [0..]))++-- | one emitter and 2 committers - IO fusion+e1c2IO :: (MonadConc m) => Cont m (Emitter (STM m) b) -> m ([b],[b])+e1c2IO e = do+  (_,c1,r1) <- cCRef+  (_,c2,r2) <- cCRef+  fuse (pure . pure) (liftB <$> (Box <$> c1 <*> e))+  fuse (pure . pure) (liftB <$> (Box <$> c2 <*> e))+  (,) <$> r1 <*> r2++exe1c2IO :: (MonadConc m) => Int -> m ([Int],[Int])+exe1c2IO n = e1c2IO (toEmit (S.take n $ S.each [0..]))++-- | test when the deterministic takes too long (which is almost always)+t :: (MonadConc n, MonadIO n, Eq b, Show b) =>+     String -> ConcT n b -> n Bool+t l c = dejafuWay (randomly (mkStdGen 42) 1000) defaultMemType l alwaysSame c++main :: IO ()+main = do+  let n = 2+  sequence_ $ autocheck <$> [exFuse n, exFuseSTM n, exEtc n]+  void $ t "e1c2" (exe1c2 10)+  void $ t "e1c2 IO" (exe1c2IO 10)++exc :: (MonadConc m) => Int -> m ([Int],[Int])+exc n = do+  ref <- newIORef 0+  let e = Emitter $ do+        a <- readIORef ref+        if a < n+          then do+            writeIORef ref (a+1)+            pure (Just a)+          else pure Nothing+  (_,c1,r1) <- cCRef+  (_,c2,r2) <- cCRef+  fuse (pure . pure) (Box <$> (liftC <$> c1) <*> pure e)+  fuse (pure . pure) (Box <$> (liftC <$> c2) <*> pure e)+  (,) <$> r1 <*> r2++eCounter :: (C.MonadConc m) => Int -> Int -> m (Emitter m Int, IORef m Int)+eCounter start n = do+  ref <- newIORef start+  pure (+    Emitter $ do+        a <- readIORef ref+        if a < n+          then do+          writeIORef ref (a+1)+          pure (Just a)+        else pure Nothing, ref)++eCounter' :: (C.MonadConc m) => Int -> Int -> m (Cont m (Emitter m Int), IORef m Int)+eCounter' start n = do+  ref <- newIORef start+  pure ( fuseEmitM $ +    Emitter $ do+        a <- readIORef ref+        if a < n+          then do+          writeIORef ref (a+1)+          pure (Just a)+        else pure Nothing, ref)++exc' :: (MonadIO m, MonadConc m) => Int -> m ([Int],[Int])+exc' n = do+  (b, c) <- broadcast'+  (ec, eref) <- eCounter 0 n +  let e1 = subscribe' b+  let e2 = subscribe' b+  fuse' (pure . pure) (pure $ Box c ec)+  (_,c1,r1) <- cCRef+  (_,c2,r2) <- cCRef+  fuse (pure . pure) (Box <$> (liftC <$> c1) <*> e1)+  fuse (pure . pure) (Box <$> (liftC <$> c2) <*> e2)+  eres <- readIORef eref+  putStrLn $ "eref: " <> (show eres :: Text)+  (,) <$> r1 <*> r2++-- | a broadcaster +newtype Broadcaster' m a = Broadcaster'+  { unBroadcast :: TVar (STM m) (Committer m a)+  }++-- | create a (broadcaster, committer)+broadcast' :: (Show a, MonadConc m, MonadIO m) => m (Broadcaster' m a, Committer m a)+broadcast' = do+  ref <- C.atomically $ newTVar mempty+  let com = Committer $ \a -> do+        putStrLn $ "broadcaster': " <> (show a :: Text)+        c <- C.atomically $ readTVar ref+        commit c a+  pure (Broadcaster' ref, com)++-- | subscribe to a broadcaster+subscribe' :: (Show a, MonadIO m, MonadConc m) => Broadcaster' m a -> Cont m (Emitter m a)+subscribe' (Broadcaster' tvar) = Cont $ \e -> queueELog cio e+  where+    cio c = C.atomically $ modifyTVar' tvar (mappend c)++-- * primitives+-- | fuse an emitter directly to a committer+fuse_' :: (Show a, MonadIO m) => Emitter m a -> Committer m a -> m ()+fuse_' e c = go+  where+    go = do+      a <- emit e+      putStrLn $ "fuse_' emit: " <> (show a :: Text)+      c' <- maybe (pure False) (commit c) a+      putStrLn $ "fuse_' commit: " <> (show c' :: Text)+      when c' go++fuse' :: (Show b, MonadIO m) => (a -> m (Maybe b)) -> Cont m (Box m b a) -> m ()+fuse' f box = with box $ \(Box c e) -> fuse_' (emap f e) c++-- exEmerge :: (MonadIO m, MonadConc m) => Int -> Int -> Int -> Int -> m [Int]+exEmerge :: MonadConc m => Int -> Int -> Int -> Int -> m ([Int], Int, Int)+exEmerge st1 st2 n1 n2 = do+  (e1, eref1) <- eCounter st1 n1+  (e2, eref2) <- eCounter st2 n2+  (_,c1,r1) <- cCRef+  fuse (pure . pure) $ Box <$> (liftC <$> c1) <*> emergeM (pure (e1, e2))+  (,,) <$> r1 <*> readIORef eref1 <*> readIORef eref2++exEmergeM :: MonadConc m => Int -> Int -> Int -> Int -> m ([Int], Int, Int)+exEmergeM st1 st2 n1 n2 = do+  (e1, eref1) <- eCounter' st1 n1+  (e2, eref2) <- eCounter' st2 n2+  (_,c1,r1) <- cCRef+  fuse (pure . pure) $ Box <$> (liftC <$> c1) <*> emergeM ((,) <$> e1 <*> e2)+  (,,) <$> r1 <*> readIORef eref1 <*> readIORef eref2+++temerge :: IO Bool+temerge = dejafuWay+    (randomly (mkStdGen 42) 1000)+    defaultMemType+    "test emergeM"+    alwaysSame+    (exEmergeM 0 0 2 2)++exCSplit :: MonadConc m => Int -> Int -> m [Int]+exCSplit st1 n1 = do+  (e1, _) <- eCounter' st1 n1+  (_,c1,r1) <- cCRef+  fuse (pure . pure) $ Box <$> (liftC <$> liftA2 (<>) c1 c1) <*> e1+  r1++contCommit' :: Either (Committer m Int) (Committer m Int) -> Committer m Int+contCommit' ec =+  Committer $ \a ->+    case ec of+      Left lc -> commit (contramap (100+) lc) a+      Right rc -> commit rc a++splitCommitM :: (MonadConc m) =>+     Cont m (Committer m a)+  -> Cont m (Either (Committer m a) (Committer m a))+splitCommitM c =+  Cont $ \kk ->+    with c $ \c' ->+      fst <$>+      C.concurrently+        (queueCM (kk . Left) (`fuse_` c'))+        (queueCM (kk . Right) (`fuse_` c'))+++counter :: (MonadState Int m) => Int -> StateT Int m (Emitter m Int)+counter n =+  pure $+    Emitter $ do+        a <- Protolude.get+        case a < n of+          False -> pure Nothing+          True -> do+            put $ a + 1+            pure (Just a)++counterT :: (Num a, Ord a, Monad m) => a -> Emitter (StateT a m) a+counterT n =+    Emitter $ do+        a <- Trans.get+        case a < n of+          False -> pure Nothing+          True -> do+            Trans.put $ a + 1+            pure (Just a)+++rememberer :: (MonadState [Int] m) => StateT [Int] m (Committer m Int)+rememberer =+  pure $+    Committer $ \a -> do+        modify (a:)+        pure True++remembererT :: (Monad m) => Committer (StateT [a] m) a+remembererT =+    Committer $ \a -> do+        Trans.modify (a:)+        pure True+++data StateExs = StateExs { count :: Int, result :: [Int]} deriving (Show, Eq, Generic)++boxCount ::+  (MonadConc m, MonadState StateExs m) =>+  Int ->+  m (Box m Int Int)+boxCount n = Box <$> pure rememberer' <*> pure counter' where+  counter' =+    Emitter $ do+        a <- use #count+        case a < n of+          False -> pure Nothing+          True -> do+            #count += 1+            pure (Just a)+  rememberer' =+    Committer $ \a -> do+        #result %= (a:)+        pure True++countEmitter :: (Ord a, Num a, MonadState s m, Data.Generics.Product.HasField "count" s s a a) => a -> Emitter m a+countEmitter n = Emitter $ do+  a <- use (field @"count")+  case a < n of+    False -> pure Nothing+    True -> do+      field @"count" += 1+      pure (Just a)++resultCommitter :: (MonadState s m, Data.Generics.Product.HasField "result" s s [a] [a]) => Committer m a+resultCommitter = Committer $ \a -> do+  field @"result" %= (a:)+  pure True++-- boxCount' :: (MonadState StateExs m, MonadConc m) => Int -> Box m Int Int+boxCount' n = Box (zoom #result resultCommitter) (zoom #count (countEmitter n))++exs :: (MonadConc m) => Int -> m [Int]+exs n = do+  (StateExs _ res) <- execStateT+    (fuse (pure . pure) (pure $ Box resultCommitter (countEmitter n)))+    (StateExs 0 [])+  pure (reverse res)++fuse'' :: (Show b, MonadIO m) => (a -> m (Maybe b)) -> Cont m (Box m b a) -> m ()+fuse'' f box = with box $ \(Box c e) -> fuse_' (emap f e) c+++-- | emitter hooked into broadcasting is broken ...+-- > exbr 2+-- ([],[])+--+broadcasting :: MonadConc m => Cont m (Emitter (STM m) b) -> m ([b], [b])+broadcasting e = do+  (b, c) <- C.atomically broadcast+  let e1 = subscribe b+  let e2 = subscribe b+  fuseSTM (pure . pure) (Box <$> pure c <*> e)+  (_,c1,r1) <- cCRef+  (_,c2,r2) <- cCRef+  fuseSTM (pure . pure) (Box <$> c1 <*> e1)+  fuseSTM (pure . pure) (Box <$> c2 <*> e2)+  (,) <$> r1 <*> r2++exbrPure :: (MonadConc m) => Int -> m ([Int],[Int])+exbrPure n = broadcasting (toEmit (S.take n $ S.each [0..]))++-- > exbrIO 2+-- 1+-- 2+-- ([],[])+--+exbrIO :: Int -> IO ([Text],[Text])+exbrIO n = broadcasting (eStdin n)++
+ test/test.hs view
@@ -0,0 +1,10 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# OPTIONS_GHC -Wall #-}++module Main where++import Protolude+import Test.DocTest++main :: IO ()+main = doctest ["src/Box.hs"]