packages feed

stc-lang (empty) → 1.0.0

raw patch · 29 files changed

+3149/−0 lines, 29 filesdep +BoundedChandep +HUnitdep +abstract-parsetup-changed

Dependencies added: BoundedChan, HUnit, abstract-par, aeson, base, bytestring, clock, deepseq, ghc-prim, hashable, hashtables, hedis, hw-kafka-client, microlens, microlens-aeson, monad-par, monad-par-extras, mtl, random, stc-lang, test-framework, test-framework-hunit, text, time, transformers, uuid-types, vector, yaml

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Norman Rink, Sebastian Ertel, Justus Adam (c) 2017-2019++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 Author name here 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.
+ README.md view
@@ -0,0 +1,26 @@+# STCLang: A library for implicit monadic dataflow parallelism++STCLang is a library that enables stateful, implicit, monadic parallel+computation in Haskell. The core ideas come from the+[ohua](https://ohua-dev.github.io) project.++STCLang lets you create parallel dataflows with stateful nodes without having to+explicitly wire complex graph structures. Instead the program is written with an+embedded, monadic DSL and automatically transformed into a graph and executed in+parallel.++On top of the base abstraction we have also built an FRP (functional reactive+programming) interface. This allows you to run reactive programs on sequential+streams of values and leverage pipeline parallelism to peed up computation.++We also [published](#publication) the theory and concepts behind this library.++## Publication++We documented the principles in this library in a paper at Haskell'2019.++A link to the publication will appear here once we have one, e.t.a. is 22th of+August (date of the conference). Should it be after this date now, but there's+still no link, I probably forgot. In that case open an issue, shoot+[me](https://github.com/JustusAdam) an email or tweet me+[@justusadam_](https://twitter.com/justusadam_).
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ src/Control/Monad/Generator.hs view
@@ -0,0 +1,270 @@+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE MonadComprehensions #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE CPP #-}++module Control.Monad.Generator+    ( IsGenerator(..)+    , liftIO+    , foldlGenerator+    , foldlGeneratorT+    , foldlGenerator_+    , foldlGeneratorT_+    , chanToGenerator+    , ioReaderToGenerator+    , foldableGenerator+    , foldableGenerator'+    , foldableGenerator''+    , foldableGeneratorEval+    , listGenerator+    , stateToGenerator+    , Generator+    -- ** A mutable IO generator variable+    , GenVar, newGenVar, pull+    ) where++import Control.Applicative+import Control.Arrow+import Control.Concurrent.Chan+import Control.Concurrent.MVar+import Control.DeepSeq+import Control.Monad.State+import Data.Foldable (foldr')+import Data.Tuple+import qualified GHC.Exts (IsList(..))+++-- | A natural transformation+type f ~> g = forall x. f x -> g x++------------------------------------------------------------------+--+-- The generator+--+------------------------------------------------------------------+-- | There are three basic ways to construct this generator, which+-- correspond to the (not exposed) constructors of this+-- type. 1. 'finish' this marks the end of a generator, 2. 'yield' is+-- used to return a value and continue with the computation afterwards+-- and finally using 'liftIO' you can execute any IO action and then+-- continue.+data Generator m a+    = Finished+    | Yield (Generator m a)+            a+    | NeedM (m (Generator m a))++-- At first I implemented all of the `Applicative` and `Monad`+-- functions fully, that is to say with all the recursion down the+-- source generator.  I was already suspecting a pattern there,+-- because the recursion for both looked very similar, I just couldn't+-- quite figure it out.  When suddenly something *magical* happened.+-- I was looking at `Alternative`, because that instance is needed for+-- `MonadComprehensions` and I was again reminded of just how similar+-- `Alternative` is to `Monoid` and I realized that `Generator` is in+-- fact a `Monoid`. The empty element is `Finished` because it can be+-- appended or prepended to any generator without changing its meaning+-- and `mappend` is simply exhausting the first generator first,+-- followed by the second one.  After having that realization+-- implementing `Monad` and `Applicative` became really easy because+-- you just simply create a new generator by applying the function and+-- then prepend a recursion of the respective operation (`>>=` or+-- `<*>`)+-- You can see the generator in action by running the examples at the bottom in ghci with `runGenerator`+mappendGen :: Functor m => Generator m a -> Generator m a -> Generator m a+Finished `mappendGen` gen2 = gen2+NeedM sc `mappendGen` gen2 = NeedM $ (`mappendGen` gen2) <$> sc+Yield g v `mappendGen` gen2 = Yield (g `mappendGen` gen2) v++instance Functor m => Monoid (Generator m a) where+    mempty = Finished+#if MIN_VERSION_base(4,11,0)+instance Functor m => Semigroup (Generator m a) where+    (<>) = mappendGen+#else+    mappend = mappendGen+#endif+instance Functor m => Functor (Generator m) where+    fmap _ Finished = Finished+    fmap f (NeedM m) = NeedM $ fmap (fmap f) m+    fmap f (Yield g a) = Yield (fmap f g) (f a)++instance Functor m => Applicative (Generator m) where+    pure = Yield Finished+    Finished <*> _ = Finished+    NeedM m <*> v = NeedM $ (<*> v) <$> m+    Yield fg f <*> v = fmap f v `mappend` (fg <*> v)++instance Functor m => Monad (Generator m) where+    return = pure+    Finished >>= _ = Finished+    NeedM m >>= f = NeedM $ (>>= f) <$> m+    Yield cont a >>= f = f a `mappend` (cont >>= f)++-- | This is needed to get the Monad comprehensions+instance Functor m => Alternative (Generator m) where+    empty = mempty+    (<|>) = mappend++-- | IO can be embedded easily+instance MonadIO m => MonadIO (Generator m) where+    liftIO = needM . liftIO++instance Monad m => GHC.Exts.IsList (Generator m a) where+    type Item (Generator m a) = a+    fromList = listGenerator+    toList _ = error "toList: need monad to evaluate generator"++foldlGeneratorT ::+       (IsGenerator g f, Monad m)+    => (f ~> m)+    -> (b -> a -> m b)+    -> b+    -> g a+    -> m b+foldlGeneratorT trans ac = flip go+  where+    go gen seed' =+        trans (step gen) >>=+        maybe (pure seed') (\(a, gen') -> go gen' =<< ac seed' a)++foldlGenerator ::+       (IsGenerator g m, Monad m) => (b -> a -> m b) -> b -> g a -> m b+foldlGenerator = foldlGeneratorT id++foldlGeneratorT_ ::+       (IsGenerator g f, Monad m) => (f ~> m) -> (a -> m ()) -> g a -> m ()+foldlGeneratorT_ trans f = foldlGeneratorT trans (\() a -> f a) ()++foldlGenerator_ :: (IsGenerator g m, Monad m) => (a -> m ()) -> g a -> m ()+foldlGenerator_ = foldlGeneratorT_ id++ioReaderToGenerator :: (IsGenerator g m, Monad g) => m (Maybe a) -> g a+ioReaderToGenerator reader = recur+  where+    recur = maybe finish (`yield` recur) =<< needM reader++chanToGenerator ::+       (MonadIO m, IsGenerator g m, Monad g) => Chan (Maybe a) -> g a+chanToGenerator = ioReaderToGenerator . liftIO . readChan++foldableGenerator :: (Foldable f, IsGenerator g m) => f a -> g a+foldableGenerator = foldr' yield finish++foldableGeneratorEval ::+       (Foldable f, IsGenerator g m) => (forall b. a -> b -> b) -> f a -> g a+foldableGeneratorEval eval = foldr (\a rest -> a `eval` yield a rest) finish++foldableGenerator' :: (Foldable f, IsGenerator g m) => f a -> g a+foldableGenerator' = foldableGeneratorEval seq++foldableGenerator'' :: (Foldable f, IsGenerator g m, NFData a) => f a -> g a+foldableGenerator'' = foldableGeneratorEval deepseq++-----------------------------------------------------------------+--+-- Creating generators+--+------------------------------------------------------------------+listGenerator :: IsGenerator g m => [a] -> g a+listGenerator = foldableGenerator++-- | A generator crated with this will run until it returns `Nothing` in which case the generator finishes+stateToGenerator ::+       (Monad g, IsGenerator g m) => StateT s m (Maybe a) -> s -> g a+stateToGenerator st s = do+    (a, s') <- needM $ runStateT st s+    maybe finish (`yield` stateToGenerator st s') a++------------------------------------------------------------------+--+-- Some more fun stuff that can be done with them+--+------------------------------------------------------------------+-- One fun thing we can do in IO is put the generator in a mutable variable and then just pull values from that.+type GenVar a = MVar (Generator IO a)++newGenVar :: Generator IO a -> IO (GenVar a)+newGenVar = newMVar++-- | This pulls a new value from this var (if possible) and updates its state+pull :: GenVar a -> IO (Maybe a)+pull =+    flip modifyMVar $+    fmap (maybe (Finished, Nothing) (second Just . swap)) . step++------------------------------------------------------------------+--+-- Some examples of comprehensions, composition and state embedding+--+------------------------------------------------------------------+permutations :: Generator IO (Int, Char)+permutations =+    [(i, c) | i <- listGenerator [0 .. 9], c <- listGenerator ['a' .. 'f']]++nonReflexivePermutations :: Int -> Generator IO (Int, Int)+nonReflexivePermutations i = [(a, b) | a <- ints, b <- ints, a /= b]+  where+    ints = listGenerator [0 .. i]++justSomeStuffWithInts :: Generator IO Int+justSomeStuffWithInts =+    flip stateToGenerator 0 $ do+        s <- get+        if s < 100+            then do+                modify (+ 4)+                pure $ Just s+            else do+                liftIO $ putStrLn "We have reached 100" -- It can do IO as well ;)+                pure Nothing++-- and they are all compatible and can be joined together (and depend on each other)+-- Probably dont run this ... it creates a **lot** of output+crazy :: Generator IO (Int, Int, Char)+crazy =+    [ (a + b, a * d, c)+    | i <- justSomeStuffWithInts+    , (b, d) <- nonReflexivePermutations i+    , (a, c) <- permutations+    ]++------------------------------------------------------------------+--+-- A generator interface+--+------------------------------------------------------------------+-- Something I thought of this morning.+-- There could also be a generic interface for generators+-- | A generator @g@ that runs in the monad @m@+class IsGenerator g m | g -> m where+    yield :: a -> g a -> g a+    finish :: g a+    needM :: m a -> g a+    isFinished :: g a -> Bool+    default isFinished :: Eq (g a) =>+        g a -> Bool+    isFinished = (== finish)+    -- | Run until the generator yields its first value or finishes.+    -- Returns the created value and a new generator which represents its updated internal state.+    step :: g a -> m (Maybe (a, g a))+    -- | Run a generator producing a list of output values+    toList :: g a -> m [a]+    default toList :: Monad m =>+        g a -> m [a]+    toList = foldlGenerator (\b a -> pure $ a : b) []++instance Monad m => IsGenerator (Generator m) m where+    yield = flip Yield+    finish = Finished+    needM = NeedM . fmap pure+    isFinished Finished = True+    isFinished _ = False+    step Finished = pure Nothing+    step (NeedM ac) = ac >>= step+    step (Yield g a) = pure $ Just (a, g)+    toList Finished = pure []+    toList (NeedM ac) = ac >>= toList+    toList (Yield g a) = (a :) <$> toList g
+ src/Control/Monad/SD.hs view
@@ -0,0 +1,33 @@+module Control.Monad.SD+  -- | Base functionality+    ( case_+    , if_+    , smap+    , runOhuaM+    , liftWithIndex+    , OhuaM+    , SF+    , SFM+  -- | STCLang re-exports+    , runSTCLang+    , liftWithState+    , STCLang+    , CollSt(..)+    , smapSTC+  -- | Signals re-exports+    , liftSignal+    , runSignals+    , filterSignalM+    , filterSignal+    , Signals+  -- | Combinators+    , mapReduce+    , mapReduceRangeThresh+    ) where++import Control.Monad.SD.Case+import Control.Monad.SD.Combinator+import Control.Monad.SD.FRP+import Control.Monad.SD.Ohua+import Control.Monad.SD.STCLang+import Control.Monad.SD.Smap
+ src/Control/Monad/SD/Case.hs view
@@ -0,0 +1,49 @@+module Control.Monad.SD.Case+    ( case_+    , if_+    ) where++import Control.Monad+import Control.Monad.IO.Class+import Control.Monad.Par.Class as PC+import Control.Monad.SD.Ohua+import Data.StateElement++import Data.List as List+import Data.Maybe++case_ ::+       forall a p. (NFData a, Show a, Eq p)+    => p+    -> [(p, OhuaM a)]+    -> OhuaM a+case_ cond patternsAndBranches = OhuaM moveState comp+  where+    moveState ::+           forall ivar m. (ParIVar ivar m, MonadIO m)+        => GlobalState ivar+        -> m (GlobalState ivar)+    moveState gs =+        (foldM (flip moveStateForward) gs . map snd) patternsAndBranches+    comp ::+           forall ivar m. (ParIVar ivar m, Monad m, MonadIO m, NFData (ivar S))+        => GlobalState ivar+        -> m (a, GlobalState ivar)+    comp gs+      -- find the first pattern that matches+     = do+        let idx = List.findIndex ((cond ==) . fst) patternsAndBranches+        let ith = fromMaybe (error "No pattern found for condition.") idx+      -- one could of course do the following in parallel but it is not a performance bottleneck as of now.+        let trueBranch = patternsAndBranches !! ith+        let falseBranches =+                ((\(before, _:after) -> before ++ after) . List.splitAt ith)+                    patternsAndBranches+        gs' <- foldM (flip moveStateForward) gs $ map snd falseBranches+        (result, gs'') <- runOhua (snd trueBranch) gs'+        return (result, gs'')++if_ :: (Show a, NFData a) => OhuaM Bool -> OhuaM a -> OhuaM a -> OhuaM a+if_ cond then_ else_ = do+    i <- cond+    case_ i [(True, then_), (False, else_)]
+ src/Control/Monad/SD/Combinator.hs view
@@ -0,0 +1,123 @@+module Control.Monad.SD.Combinator where++import Control.Monad+import Control.Monad.Generator+import Control.Monad.Par.Class as PC+import Control.Monad.Par.Combinator (InclusiveRange, InclusiveRange(..))+import Control.Monad.SD.Ohua+import Control.Monad.SD.STCLang+import Control.Monad.SD.Smap+import Data.Dynamic2+import Data.StateElement++import Control.Monad.State as S+import Data.List as List+  ----+  -- The below comes originally from: https://hackage.haskell.org/package/monad-par-extras-0.3.3/docs/src/Control-Monad-Par-Combinator.html#parMapReduceRangeThresh+  ----+  -- | Computes a binary map\/reduce over a finite range.  The range is+  -- recursively split into two, the result for each half is computed in+  -- parallel, and then the two results are combined.  When the range+  -- reaches the threshold size, the remaining elements of the range are+  -- computed sequentially.+  --+  -- For example, the following is a parallel implementation of+  --+  -- >  foldl (+) 0 (map (^2) [1..10^6])+  --+  -- > parMapReduceRangeThresh 100 (InclusiveRange 1 (10^6))+  -- >        (\x -> return (x^2))+  -- >        (\x y -> return (x+y))+  -- >        0+  --+  -- parMapReduceRangeThresh ::+  --      (NFData a, ParFuture iv p)+  --   => Int -- ^ threshold+  --   -> InclusiveRange -- ^ range over which to calculate+  --   -> (Int -> p a) -- ^ compute one result+  --   -> (a -> a -> p a) -- ^ combine two results (associative)+  --   -> a -- ^ initial result+  --   -> p a+  -- parMapReduceRangeThresh threshold range fn binop init =+  -- loop min max+  -- where+  --   loop min max+  --     | max - min <= threshold =+  --       let mapred a b = do+  --             x <- fn b+  --             result <- a `binop` x+  --             return result+  --        in foldM mapred init [min .. max]+  --     | otherwise = do+  --       let mid = min + ((max - min) `quot` 2)+  --       rght <- spawn $ loop (mid + 1) max+  --       l <- loop min mid+  --       r <- get rght+  --       l `binop` r++instance Show InclusiveRange++mapReduceRangeThresh ::+       (NFData a, Typeable a, Show a)+    => Int -- ^ threshold+    -> InclusiveRange -- ^ range over which to calculate+    -> (Int -> a) -- ^ compute one result+    -> (a -> a -> a) -- ^ combine two results (associative)+    -> a -- ^ initial result+    -> IO a+mapReduceRangeThresh threshold range fn binop init+    -- sadly I could not use STCLang to build this :(+    -- reason: it must be STCLang a b to implement liftSignal instead of just+    -- STCLang b just like OhuaM b+    -- (_, [reduceState]) <- runOhuaM mapReduce [toS init]+    -- return $ fromS reduceState+ = do+    (_, [reduceState]) <- runSTCLang mapReduce chunkGenerator+    return $ fromS reduceState+  where+    mapReduce = do+        reduceST <- liftWithState (return init) reduce+        -- return $\x -> smapGen ((pure . mapAndCombine) >=> reduceST) x+        return $ smapGen ((pure . mapAndCombine) >=> reduceST)+      -- mapReduce = do+      --   smapGen+      --     ((pure . mapAndCombine) >=> liftWithIndex 0 reduce)+      --     chunkGenerator+    chunkGenerator :: Generator IO InclusiveRange+    chunkGenerator =+        flip stateToGenerator range $ do+            (InclusiveRange mi ma) <- S.get+            if mi >= ma+                then return Nothing+                else let mi' = min (mi + threshold) ma+                      in do S.put $ InclusiveRange (mi' + 1) ma+                            return $ Just $ InclusiveRange mi mi'+    list (InclusiveRange mi ma)+        | mi >= ma = []+        | otherwise = InclusiveRange mi mi' : list (InclusiveRange (mi' + 1) ma)+      where+        mi' = min (mi + threshold) ma+    mapAndCombine (InclusiveRange mi ma) =+        let mapred a b =+                let x = fn b+                    result = a `binop` x+                 in result+         in List.foldl mapred init [mi .. ma]+    reduce v = S.get >>= (S.put . (`binop` v))+  --{-# INLINE parMapReduceRangeThresh #-}+  -- streams output from the map phase to the reduce phase++mapReduce ::+       (NFData a, NFData b, Typeable b, Show a, Show b)+    => (a -> b)+    -> (b -> b -> b)+    -> b+    -> [a]+    -> IO b+mapReduce mapper reducer init xs = do+    (_, [reduceState]) <- runOhuaM algo [toS init]+    return $ fromS reduceState+  where+    algo = smapGen (pure . mapper >=> liftWithIndex 0 reduce) $ listGenerator xs+    reduce v = S.get >>= (S.put . (`reducer` v))+  --{-# INLINE mapReduce #-}
+ src/Control/Monad/SD/FRP.hs view
@@ -0,0 +1,92 @@+module Control.Monad.SD.FRP+    ( liftSignal+    , runSignals+    , filterSignalM+    , filterSignal+    , Signals+    ) where++import Control.Monad.Generator+import Control.Monad.SD.Case+import Control.Monad.SD.Ohua+import Control.Monad.SD.STCLang+import Control.Monad.SD.Smap+import Data.Dynamic2+import Data.StateElement++import qualified Control.Concurrent as Conc+import qualified Control.Concurrent.BoundedChan as BC+import Control.DeepSeq (NFData)+import Control.Exception (bracket)+import Control.Monad.State as S+import System.IO (hPutStrLn, stderr)++type Signal = IO++type Signals = (Int, S)++instance Show S where+    show _ = "S"++liftSignal :: (Typeable a, NFData a) => Signal a -> IO a -> STCLang Signals a+liftSignal s0 init = do+    idx <-+        S.state $ \s@CollSt {signals} ->+            (length signals, s {signals = signals ++ [toS <$> s0]})+    liftWithState init $ \(i, s) ->+        if i == idx+            then do+                let my = fromS s+                S.put my+                pure my+            else S.get++debugSignals :: Bool+debugSignals = True++printSignalD :: MonadIO m => String -> m ()+printSignalD+    | debugSignals = liftIO . hPutStrLn stderr+    | otherwise = const $ pure ()++runSignals :: NFData a => STCLang Signals a -> IO ([a], [S])+runSignals comp = do+    printSignalD "Running STCLang"+    (comp', s) <- S.runStateT comp mempty+    chan <- BC.newBoundedChan 100+    bracket+        (do printSignalD "Starting signals... "+            forM (zip [0 ..] $ signals s) $ \(idx, sig) ->+                Conc.forkIO $+                forever $ do+                    event <- sig+                    BC.writeChan chan $ Just (idx, event))+        (\threads -> do+             printSignalD "Killing signal threads"+             mapM_ Conc.killThread threads)+        (\_ -> do+             putStrLn "signals done"+             let signalGen = ioReaderToGenerator (BC.readChan chan)+             runOhuaM (smapGen comp' signalGen) $ states s)++filterSignalM ::+       (Show b, NFData a, NFData b)+    => (a -> OhuaM Bool)+    -> (a -> OhuaM b)+    -> STCLang a (Maybe b)+filterSignalM cond f =+    pure $ \item -> if_ (cond item) (Just <$> f item) (pure Nothing)+    -- | @filter init p f@ applies @f@ to only those values @a@ that satisfy the+    -- predicate @p@. For values not satisfying it returns the last computed value+    -- (initially @init@)++filterSignal ::+       (Show b, Typeable b, NFData b, NFData a)+    => IO b -- Initial value for the output+    -> (a -> OhuaM Bool) -- predicate+    -> (a -> OhuaM b) -- computation to perform on `a`+    -> STCLang a b+filterSignal init cond f = do+    g <- liftWithState init $ maybe S.get (\i -> S.put i >> pure i)+    fil <- filterSignalM cond f+    return $ fil >=> g
+ src/Control/Monad/SD/Ohua.hs view
@@ -0,0 +1,341 @@+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE CPP #-}++--- this implementation does not rely on channels. it builds on futures!+module Control.Monad.SD.Ohua+    ( liftWithIndex+    , liftWithIndex'+    , SF+    , SFM+    , runOhuaM+    , OhuaM(..)+    , GlobalState(..)+    ) where++import Control.Monad++-- import           Control.Monad.Par       as P+import Control.Arrow (first)+import Control.Monad.Par.Class as PC++import Control.Monad.Par.IO as PIO+#ifdef DEBUG_SCHED+import qualified Control.Monad.Par.Scheds.TraceDebuggable as TDB+#endif+import Control.Monad.State as S++--+-- for debugging only:+-- import Debug.Scheduler as P+--+-- import           Control.Parallel    (pseq)+import Data.Dynamic2+import Data.StateElement++import Control.DeepSeq (deepseq)++import GHC.Generics (Generic)+import GHC.Stack (HasCallStack)++-- type SFM s b = State s b+type SFM s b = StateT s IO b++type SF s a b = a -> SFM s b++-- runSF :: SFM s b -> s -> (b,s)+-- runSF = runState+runSF :: SFM s b -> s -> IO (b, s)+runSF = runStateT++-- data OhuaM m globalState result = OhuaM {+--                               moveStateForward :: globalState -> m globalState,+--                               runOhua :: globalState -> m (result, globalState)+--                              }+-- this existential quantification essentially hides the types for ivar and m.+-- this forces somebody with a variable of that type to apply it only to a predefined+-- function that knows what the type of 'ivar' and 'm' is.+-- this way, the types are entirely hidden inside that module and restrict the user/caller+-- to a very specific function, i.e., runOhua and moveStateForward.+-- I love that!+-- sources: https://prime.haskell.org/wiki/ExistentialQuantification+--          https://stackoverflow.com/questions/12031878/what-is-the-purpose-of-rank2types#12033549+-- data OhuaM state result = forall ivar m. (ParIVar ivar m)+--                         => OhuaM {+--                              moveStateForward :: GlobalState ivar state -> m (GlobalState ivar state),+--                              runOhua :: GlobalState ivar state -> m (result, GlobalState ivar state)+--                             }+-- when the data constructor OhuaM is called then the type variables are+-- captured with the according types. when the according functions are called later on, then+-- the input to that call must match the captured types now.+-- the above version quantifies over the whole creation of the data type. it becomes:+-- forall ivar m. (ParIVar ivar m) => ((GlobalState ivar state) -> m (GlobalState ivar state))+--                                 -> ((GlobalState ivar state) -> m (result, GlobalState ivar state))+--                                 -> OhuaM state result+-- but we want to have Rank2Types instead to hide ivar and m! (see the example below!)+data OhuaM result = OhuaM+    { moveStateForward :: forall ivar m. (ParIVar ivar m, MonadIO m) =>+                                             GlobalState ivar -> m (GlobalState ivar)+    , runOhua :: forall ivar m. ( ParIVar ivar m+                                , MonadIO m+                                , NFData (ivar S) -- FIXME giving the MonadIO constraint here seems weird to me because then it totally breaks the abstraction and could write ParIO directly.+                                ) =>+                                    GlobalState ivar -> m ( result+                                                          , GlobalState ivar)+    }++-- Example: ExistentialQuantification vs Rank2Types+-- Prelude> set: -XExistentialQuantification+-- Prelude> data T s r = forall ivar m. (Show ivar, Monad m) => TR { f :: (s,ivar) -> m (ivar,s) }+-- Prelude> :t TR+-- TR :: (Monad m, Show ivar) => ((s, ivar) -> m (ivar, s)) -> T s r+-- that is:+-- TR :: forall ivar m. (Monad m, Show ivar) => ((s, ivar) -> m (ivar, s)) -> T s r+-- BUT:+-- Prelude> set: -Rank2Types+-- Prelude> data T s r = TR { f :: forall ivar m. (Show ivar, Monad m) => (s,ivar) -> m (ivar,s) }+-- translates to:+-- Prelude> :t TR+-- TR :: (forall ivar (m :: * -> *). (Show ivar, Monad m) => (s, ivar) -> m (ivar, s)) -> T s r+--+-- ExistentialQuantification makes only sense when we quantify over the output of a function (i.e.)+-- the type of a record. that is because each function captures its own type variable so you can not+-- compose such data as I tried in <*> or =<< with GlobalState (which came from another data).+data GlobalState ivar = GlobalState+    { input :: [ivar S]+    , result :: [ivar S]+    } deriving (Generic)++-- data GlobalState ivar = GlobalState [ivar S] [ivar S] deriving (Generic)+instance (NFData (ivar S)) => NFData (GlobalState ivar)++--+-- shortcoming: this monad implementation is strict because bind requests the+--              actual value. consider the following situation:+--              do+--                 x1 <- a 5+--                 x2 <- b 5+--                 x3 <- c 5+--              this monad will run these 3 statements in sequence because bind+--              always wants the concrete value although it may not actually be+--              used by the directly following computation. to circumvent this+--              case, one would have to use an applicative here:+--              do+--                (x1,x2,x3) <- (,,) <$> a 5 <*> a 5 <*> a 5+--+instance Functor OhuaM where+    fmap f g = OhuaM (moveStateForward g) $ fmap (first f) . runOhua g++instance Applicative OhuaM where+    pure = return+  -- TODO (<*>) = Control.Monad.ap  this is a requirement if the functor is also a monad.+  -- this is the case so we should create a new functor that is not a monad but only an applicative.+  -- in order to do so we need to provide a OhuaM computation in the new applicative functor that+  -- can be ready executed via runOhua! - (Haxl doesn't care)+    (<*>) :: forall a b. OhuaM (a -> b) -> OhuaM a -> OhuaM b+    f <*> a = OhuaM moveState comp+      where+        moveState ::+               forall ivar m. (ParIVar ivar m, MonadIO m)+            => GlobalState ivar+            -> m (GlobalState ivar)+        moveState gs+        -- there is really no computation here, so no need to spawn anything+         = do+            gs' <- moveStateForward a gs+            moveStateForward f gs'+        -- there is no state change here really. I could have returned gs' as well, I suppose.+        comp ::+               forall ivar m. (ParIVar ivar m, MonadIO m, NFData (ivar S))+            => GlobalState ivar+            -> m (b, GlobalState ivar)+        comp gs+        -- run the action first. in the final monad code for OhuaM, the outermost <*>+        -- will execute first. as a result of this code, we will recursively go and+        -- spawn the tasks for the arguments which can happily execute in parallel+        -- until we reach the bottom of the recursion, i.e., the pure function.+        -- then the recursion unwinds again gathering all the results.+         = do+            aVar <- PC.spawn_ $ runOhua a gs -- TODO force evaluation here+        -- run the function+            (fResult, _) <- runOhua f gs+        -- wrap it up by applying the function to the result of the action+            (r, gs') <- PC.get aVar+            return (fResult r, gs')+  -- mf@(OhuaM _) <*> mv@(OhuaM _) = Collected mf [mv]+  -- mf@(OhuaM _) <*> (Collected pf sfs) = Collected mf (pf : sfs)+  -- (Collected pf sfs) <*> mv@(OhuaM sf) = Collected pf sfs ++ [mv]+  -- (Collected pf1 sfs1) <*> (Collected pf2 sfs2) = Collected pf1 (sfs1 ++ (pf2:sfs2))+  --  -- this collecting is only stopped by the monadic bind operator!++instance Monad OhuaM+  --{-# NOINLINE return #-}+                            where+    return :: forall a. a -> OhuaM a+    return v = OhuaM return $ \s -> return (v, s)+    {-# NOINLINE (>>=) #-}+    (>>=) :: HasCallStack => OhuaM a -> (a -> OhuaM b) -> OhuaM b+    f >>= g =+            OhuaM moveState comp+          where+        moveState ::+               forall ivar m. (ParIVar ivar m, MonadIO m, HasCallStack)+            => GlobalState ivar+            -> m (GlobalState ivar)+        moveState gs = do+            gs' <- moveStateForward f gs+            flip moveStateForward gs' $+                g $+                error+                    "Invariant broken: Don't touch me, state forward moving code!"+      -- comp ::+      --      forall ivar m. (ParIVar ivar m, MonadIO m, NFData (ivar S))+      --   => GlobalState ivar+      --   -> m (b, GlobalState ivar)+        comp gs+          -- there is no need to spawn here!+          -- pipeline parallelism is solely created by smap.+          -- task-level parallelism is solely created by <*>+         = do+            (result0, gs') <- runOhua f gs+            (result1, gs'') <- runOhua (g result0) gs'+            return (result1, gs'')+        {-# INLINE comp #-}++instance MonadIO OhuaM where+    liftIO :: IO a -> OhuaM a+    liftIO ioAction = OhuaM return $ \s -> (, s) <$> liftIO ioAction+    {-# INLINE liftIO #-}++--{-# NOINLINE liftWithIndex #-}+{-# INLINE liftWithIndex #-}+liftWithIndex ::+       (NFData a, Show a, NFData s, Typeable s)+    => Int+    -> SF s a b+    -> a+    -> OhuaM b+liftWithIndex = liftWithIndexS++--liftWithIndex i f d = liftWithIndex' i $ f d+liftWithIndexS ::+       forall a s b. (Show a, NFData s, Typeable s, NFData a)+    => Int+    -> SF s a b+    -> a+    -> OhuaM b+liftWithIndexS i f d = OhuaM (moveState d) (compAndMoveState $ f d)+  where+    compAndMoveState ::+           forall ivar m a. (ParIVar ivar m, MonadIO m)+        => SFM s a+        -> GlobalState ivar+        -> m (a, GlobalState ivar)+    compAndMoveState sf (GlobalState gsIn gsOut)+      -- we define the proper order on the private state right here!+     = do+        let ithIn = gsIn !! i+            ithOut = gsOut !! i+      -- if we do not deepseq here then a previous parallel stage will get+      -- serialized at this point because the monadic operation will always+      -- be evaluated first and then the computation that computes the input+      -- for this algo.+        d `deepseq` pure ()+        localState <- getState ithIn -- this synchronizes access to the local state+        (d', localState') <- liftIO $ runSF sf $ fromS localState+        release ithOut $ toS localState'+        return (d', GlobalState gsIn gsOut)+    moveState ::+           forall ivar m a. (ParIVar ivar m, MonadIO m)+        => a+        -> GlobalState ivar+        -> m (GlobalState ivar)+    moveState token (GlobalState gsIn gsOut) = do+        let ithIn = gsIn !! i+            ithOut = gsOut !! i+        localState <- getState ithIn+        (_, localState') <- return (d, localState) -- id+        release ithOut localState'+      -- I'd love to be able to do something like this, but I can't catch exceptions here.+      -- release ithOut localState'  `catch` \e@ErrorCall{} ->+      --     if isMultiplePutErr e+      --     then error $ "Double use of index " ++ show i ++ " detected"+      --     else throw e+        return $ GlobalState gsIn gsOut+    idSf :: SFM s ()+    idSf = return ()+    {-# INLINE idSf #-}+    -- This match is extracted from the `shed` function in+    -- `Control.Monad.Par.Scheds.TraceInternal`+    -- isMultiplePutErr (ErrorCall msg) = msg == "multiple put"++{-# INLINE liftWithIndex' #-}+liftWithIndex' ::+       forall s b. (NFData s, Typeable s)+    => Int+    -> SFM s b+    -> OhuaM b+liftWithIndex' i comp =+    OhuaM (fmap snd . compAndMoveState idSf) (compAndMoveState comp)+  where+    compAndMoveState ::+           forall ivar m a. (ParIVar ivar m, MonadIO m)+        => SFM s a+        -> GlobalState ivar+        -> m (a, GlobalState ivar)+    compAndMoveState sf (GlobalState gsIn gsOut)+      -- we define the proper order on the private state right here!+     = do+        let ithIn = gsIn !! i+            ithOut = gsOut !! i+        localState <- getState ithIn -- this synchronizes access to the local state+        (d', localState') <- liftIO $ runSF sf $ fromS localState+        release ithOut $ toS localState'+        return (d', GlobalState gsIn gsOut)+    idSf :: SFM s ()+    idSf = return ()+    {-# INLINE idSf #-}++--{-# NOINLINE release #-}+release :: (NFData s, ParIVar ivar m) => ivar s -> s -> m ()+release = updateState++{-# INLINE release #-}+{-# INLINE updateState #-}+{-# INLINE getState #-}+updateState :: (NFData s, ParIVar ivar m) => ivar s -> s -> m ()+updateState = PC.put++getState :: (ParFuture ivar m) => ivar s -> m s+getState = PC.get -- will wait for the value+#ifdef DEBUG_SCHED+-- for debugging the scheduler+runParComp = TDB.runParIO+#else+runParComp = runParIO+#endif+runOhuaM :: (NFData a) => OhuaM a -> [S] -> IO (a, [S])+runOhuaM comp initialState =+    runParComp $ do+        inState <- mapM PC.newFull initialState+        outState <- forM initialState $ const PC.new+        (result, _) <- runOhua comp $ GlobalState inState outState+        finalState <- mapM getState outState+        return (result, finalState)+-- envisioned API:+--+-- s1 = liftWithIndex 5 $ \ x -> ....+-- OhuaM ..+-- do+--   r0 <- a x+--   r1 <- b x+--   r2 <- c x+--   xs <- d r2+--   <- smap c xs+--+--   where c x = do+--                r01 <- e x+--                r02 <- f r01+--                return r02+--+-- runOhua m s
+ src/Control/Monad/SD/STCLang.hs view
@@ -0,0 +1,48 @@+module Control.Monad.SD.STCLang+    ( STCLang+    , liftWithState+    , runSTCLang+    , CollSt(..)+    , smapSTC+    ) where++import Control.Monad.SD.Ohua+import Control.Monad.SD.Smap+import Data.Dynamic2+import Data.StateElement++import Control.DeepSeq (NFData)+import Control.Monad.State as S++data CollSt = CollSt+    { states :: [S]+    , signals :: [IO S]+    }++instance Monoid CollSt where+    mempty = CollSt [] []+    CollSt st1 si1 `mappend` CollSt st2 si2 =+        CollSt (st1 `mappend` st2) (si1 `mappend` si2)++type STCLang a b = StateT CollSt IO (a -> OhuaM b)++liftWithState ::+       (Typeable s, NFData a, NFData s, Show a)+    => IO s+    -> (a -> StateT s IO b)+    -> STCLang a b+liftWithState state stateThread = do+    s0 <- lift state+    l <- S.state $ \s -> (length $ states s, s {states = states s ++ [toS s0]})+    pure $ liftWithIndex l stateThread++runSTCLang :: (NFData b) => STCLang a b -> a -> IO (b, [S])+runSTCLang langComp a = do+    (comp, gs) <- S.runStateT langComp mempty+    runOhuaM (comp a) $ states gs++smapSTC ::+       forall a b. (NFData b, Show a)+    => STCLang a b+    -> STCLang [a] [b]+smapSTC comp = smap <$> comp
+ src/Control/Monad/SD/Smap.hs view
@@ -0,0 +1,215 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE CPP #-}++module Control.Monad.SD.Smap+    ( smap+    , smapGen+    ) where++import Control.Monad+import Control.Monad.Generator+import Control.Monad.IO.Class+import Control.Monad.Par.Class as PC+import Control.Monad.SD.Ohua+import Data.StateElement++-- FIXME this should be based on smapGen!+-- this spawns the computations for the elements but integrates the+-- state dependencies!+-- version used for debugging:+-- smap :: (NFData b, NFData s, Show a, ParIVar ivar m, NFData (ivar s)) => (Int -> a -> OhuaM m (GlobalState ivar s) b) -> [a] -> OhuaM m (GlobalState ivar s) [b]+--{-# NOINLINE smap #-}+--{-# INLINE smap #-}+smap ::+       forall a b. (NFData b, Show a)+    => (a -> OhuaM b)+    -> [a]+    -> OhuaM [b]+smap algo xs =+    case xs of+        [] -> OhuaM moveState (fmap (([] :: [b]), ) . moveState) -- if no data was given then just move the state.+        _ -> OhuaM moveState comp+    -- all we need to do is to move the state once, no need to do it for each+    -- of the elements in the array!+  where+    moveState ::+           forall ivar m. (ParIVar ivar m, MonadIO m)+        => GlobalState ivar+        -> m (GlobalState ivar)+    moveState = moveStateForward $ algo (error "I do not want to be touched!")+    comp ::+           forall ivar m. (ParIVar ivar m, MonadIO m, NFData (ivar S))+        => GlobalState ivar+        -> m ([b], GlobalState ivar)+    comp (GlobalState gsIn gsOut) = do+        futures <- smap' algo gsOut gsIn xs+        results <- forM futures PC.get -- collect the results+        let result = map fst results+        return (result, GlobalState gsIn gsOut)+    -- This function replicates the state as many times as their are values in+    -- the list and spawns the computation.+    smap' ::+           (NFData b, Show a, ParIVar ivar m, MonadIO m, NFData (ivar S))+        => (a -> OhuaM b)+        -> [ivar S]+        -> [ivar S]+        -> [a]+        -> m [ivar (b, GlobalState ivar)]+    smap' f originalOut initialState = go initialState+      where+        newEmptyStateVec = sequence $ replicate stateVSize PC.new -- create the new output state+        stateVSize = length initialState+        go prevState l =+            case l of+                [] -> error "I should be unreachable"+                [y] -> pure <$> spawnComp y originalOut+                (y:ys) -> do+                    stateVec <- newEmptyStateVec+                    (:) <$> spawnComp y stateVec <*> go stateVec ys+          where+            spawnComp e stateVec =+                PC.spawn $ runOhua (f e) $ GlobalState prevState stateVec++type AlgoRunner m ivar t result+     --(ParIVar ivar m, MonadIO m, MonadIO ivar) =>+     = t -> [ivar S] -> [ivar S] -> m (ivar (result, GlobalState ivar))++type PipelineStrategy a b+     = forall m ivar. (ParIVar ivar m, MonadIO m) =>+                          AlgoRunner m ivar a b -- algo runner+                           -> Int -- state vector size+                               -> [ivar S] -- final state vector+                                   -> [ivar S] -- current state vector+                                       -> Generator IO a -> a -> m [ivar ( b+                                                                         , GlobalState ivar)]++-- TODO: Check if this can deal with empty generators. Furthermore+-- it always advances the generator one position more than what it+-- currently processes to find the end of the generator before the+-- last item is processed so that it can spawn that computation with+-- the original output state vector.+smapGen ::+       forall a b. (NFData b, Show a)+    => (a -> OhuaM b)+    -> Generator IO a+    -> OhuaM [b]+#ifdef UNTHROTTLED+smapGen = smapGenInternal unthrottledPipe+#else+smapGen = smapGenInternal throttledPipe+#endif+smapGenInternal ::+       forall a b. (NFData b, Show a)+    => PipelineStrategy a b+    -> (a -> OhuaM b)+    -> Generator IO a+    -> OhuaM [b]+smapGenInternal pipelineStrategy algo gen =+    OhuaM moveState $ \g@(GlobalState gsIn gsOut) ->+        liftIO (step gen) >>= \case+            Nothing -> fmap (([] :: [b]), ) $ moveState g+            Just (a, gen') -> do+                futures <- spawnFutures gsOut gsIn gen' a+                values <- mapM PC.get futures+                pure (map fst values, GlobalState gsIn gsOut)+  where+    spawnFutures lastStateOut = pipelineStrategy runAlgo stateVSize lastStateOut+      where+        stateVSize = length lastStateOut+        runAlgo e stateIn stateOut =+            PC.spawn $ runOhua (algo e) $ GlobalState stateIn stateOut+    moveState ::+           forall ivar m. (ParIVar ivar m, MonadIO m)+        => GlobalState ivar+        -> m (GlobalState ivar)+    moveState = moveStateForward $ algo (undefined :: a)++newEmptyStateVec size = sequence $ replicate size PC.new++unthrottledPipe :: PipelineStrategy a b+unthrottledPipe runAlgo stateVSize lastStateOut stateIn gen' a =+    liftIO (step gen') >>= \case+        Nothing -> pure <$> runLastAlgo+        Just (a', gen'') -> do+            newStateVec <- newEmptyStateVec stateVSize+      -- the parallelism is in the applicative.+      -- runAlgo immediately returns and gives me an IVar.+      -- go is the recursion.+      -- I need to change `go` to take the current list of IVars.+      -- Then a simple version of throttling becomes totally easy.+      -- I just need to check the length of the list and once it has+      -- reached the predefined threshold, I need to stop and wait for+      -- the IVar at the head of the list before contiuing to spawn.+      -- (This assumes that the head is the one finishing first.)+            (:) <$> runAlgo a stateIn newStateVec <*>+                unthrottledPipe+                    runAlgo+                    stateVSize+                    lastStateOut+                    newStateVec+                    gen''+                    a'+  where+    runLastAlgo = runAlgo a stateIn lastStateOut++limit :: Int+limit = 10++throttledPipe :: PipelineStrategy a b+throttledPipe runAlgo stateVSize lastStateOut stateIn gen a+    -- 1. get the first n+ = do+    (genLimited, a', lastLimitOut, firstResults) <-+        unthrottled limit [] stateIn gen a+    -- 2. get on head of results before spawning a new computation+    throttled firstResults 0 lastLimitOut genLimited a'+    -- unthrottled ::+    --      Int+    --   -> [ivar (b, GlobalState ivar)]+    --   -> [ivar S]+    --   -> [ivar S]+    --   -> Generator IO a+    --   -> a+    --   -> m (Generator IO a, a, [ivar S], [ivar (b, GlobalState ivar)])+  where+    unthrottled l results sIn gen' a' = do+        if l == 0+            then return (gen', a', sIn, results)+            else do+                liftIO (step gen') >>= \case+                    Nothing -> do+                        res <- runAlgo a' sIn lastStateOut+              -- from now on the generator always returns NOTHING, so it is+              -- ok to use it as the state input vector to the next iteration.+                        return (gen', a', lastStateOut, results ++ [res])+                    Just (a'', gen'') -> do+                        newStateVec <- newEmptyStateVec stateVSize+                        resultFuture <- runAlgo a' sIn newStateVec+                        unthrottled+                            (l - 1)+                            (results ++ [resultFuture])+                            newStateVec+                            gen''+                            a''+    -- throttled ::+      --    [ivar (b, GlobalState ivar)]+      -- -> Int+      -- -> [ivar S]+      -- -> Generator IO a+      -- -> a+      -- -> m [ivar (b, GlobalState ivar)]+    throttled results lastPending sIn gen' a' = do+        _ <- PC.get $ results !! lastPending -- throttling+        liftIO (step gen') >>= \case+            Nothing -> do+                res <- runAlgo a' sIn lastStateOut+                return $ results ++ [res]+            Just (a'', gen'') -> do+                newStateVec <- newEmptyStateVec stateVSize+                ivar <- runAlgo a' sIn newStateVec+                throttled+                    (results ++ [ivar])+                    (lastPending + 1)+                    newStateVec+                    gen''+                    a''
+ src/Control/Monad/Stream.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE TypeFamilies #-}+module Control.Monad.Stream where++import Control.Monad.IO.Class++class MonadIO m =>+      MonadStream m+    where+    type Sender m :: * -> *+    type Reciever m :: * -> *+    -- | Create a stream with a end that can only be sent to and one+    -- that can only be read from.+    createStream :: m (Sender m a, Reciever m a)+    send :: a -> Sender m a -> m ()+    recieve :: Reciever m a -> m a+    spawn :: m () -> m ()
+ src/Control/Monad/Stream/Chan.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE TupleSections, TypeFamilies, RankNTypes, GeneralizedNewtypeDeriving #-}+module Control.Monad.Stream.Chan where++import Control.Concurrent.Chan+import Control.Concurrent+import Control.Arrow ((&&&))+import Control.Monad (void)+import Control.Monad.IO.Class++import Control.Monad.Stream++newtype ChanM a = ChanM+    { runChanM :: (IO a)+    } deriving (Functor, Applicative, Monad, MonadIO)++newtype S a = S { unS :: a -> ChanM () }++instance MonadStream ChanM where+    type Sender ChanM = S+    type Reciever ChanM = ChanM+    createStream = (S . (liftIO .) . writeChan &&& liftIO . readChan) <$> liftIO newChan+    send a f = (unS f) a+    recieve ac = ac+    spawn = ChanM . void . forkIO . runChanM
+ src/Control/Monad/Stream/Par.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE TypeFamilies #-}+module Control.Monad.Stream.Par+    ( S+    , ParIO+    , runParIO+    ) where++import Control.Monad.Par.IO as P+import Control.Monad.Par.Class as P+import Data.IORef+import Control.Monad.IO.Class++import Control.Monad.Stream++data Cons a = Cons a (IVar (Cons a)) ++newtype S a = S { unS :: IORef (IVar (Cons a)) }++instance MonadStream ParIO where+    type Sender ParIO = S+    type Reciever ParIO = S+    createStream = do+        v <- new+        in_ <- liftIO $ newIORef v+        out <- liftIO $ newIORef v+        pure (S in_, S out)++    -- This implementation is inherently unsafe. We use IORef and+    -- non-atomic operations to update them. This only works if the+    -- sender and receiver are never accessed simultaneously from two+    -- threads, which shouldn't happen because of the way the runtime+    -- is written. It could be made safe by using a Maybe and atomic+    -- operations, however I expect this will cause slowdown which, as+    -- the runtime should make sure this thing is impossible anyways,+    -- I am not willing to risk.+    send v =+        withS $ \var -> do+            next <- new+            put_ var $ Cons v next+            pure ((), next)+    recieve =+        withS $ \var -> do+            Cons v next <- get var+            pure (v, next)+    spawn = P.fork+++withS :: MonadIO m => (IVar (Cons a) -> m (b, IVar (Cons a))) -> S a -> m b+withS ac (S ref) = do+    var <- liftIO $ readIORef ref+    (val, var') <- ac var+    liftIO $ writeIORef ref var'+    pure val
+ src/Control/Monad/Stream/PinnedChan.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE TupleSections, TypeFamilies, RankNTypes, InstanceSigs, FlexibleInstances #-}+module Control.Monad.Stream.PinnedChan where++import Control.Concurrent.Chan+import Control.Concurrent+import Control.Arrow ((&&&))+import Control.Monad.IO.Class++import Control.Monad.Stream++import Control.Monad.State.Lazy++type PChanM = StateT Int IO++newtype S a = S { unS :: a -> PChanM () }++instance MonadStream PChanM where+    type Sender PChanM = S+    -- FIXME Receiver+    type Reciever PChanM = PChanM++    createStream :: PChanM (Sender PChanM a, Reciever PChanM a)+    createStream = (S . (liftIO .) . writeChan &&& liftIO . readChan) <$> liftIO newChan++    send :: a -> Sender PChanM a -> PChanM ()+    send a f = (unS f) a++    -- FIXME receive+    recieve :: Reciever PChanM a -> PChanM a+    recieve ac = ac++    spawn :: PChanM () -> PChanM ()+    spawn comp = do+      let ioComp = evalStateT comp 0+      cap    <- get+      defCap <- liftIO $ getNumCapabilities+      -- liftIO $ putStrLn $ "forking on cap num: " ++ (show (cap `mod` defCap) )+      _      <- liftIO $ forkOn cap ioComp+      put $ cap + 1+      return ()
+ src/Data/Dynamic2.hs view
@@ -0,0 +1,167 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE Trustworthy #-}+{-# LANGUAGE CPP #-}++-----------------------------------------------------------------------------+-- |+-- Module      :  Data.Dynamic+-- Copyright   :  (c) The University of Glasgow 2001+-- License     :  BSD-style (see the file libraries/base/LICENSE)+--+-- Maintainer  :  libraries@haskell.org+-- Stability   :  experimental+-- Portability :  portable+--+-- The Dynamic interface provides basic support for dynamic types.+--+-- Operations for injecting values of arbitrary type into+-- a dynamically typed value, Dynamic, are provided, together+-- with operations for converting dynamic values into a concrete+-- (monomorphic) type.+--+-----------------------------------------------------------------------------+module Data.Dynamic2+        -- Module Data.Typeable re-exported for convenience+    ( module Data.Typeable+        -- * The @Dynamic@ type+    , Dynamic(..) -- abstract, instance of: Show, Typeable+        -- * Converting to and from @Dynamic@+    , toDyn+    , fromDyn+    , fromDynamic+        -- * Applying functions of dynamic type+    , dynApply+    , dynApp+    , dynTypeRep+    , forceDynamic+    , TypeCastException(..)+    ) where++import Data.Maybe+import Data.Typeable+import Unsafe.Coerce++import GHC.Base+import GHC.Exception+import GHC.Show++-------------------------------------------------------------+--+--              The type Dynamic+--+-------------------------------------------------------------+{-|+  A value of type 'Dynamic' is an object encapsulated together with its type.++  A 'Dynamic' may only represent a monomorphic value; an attempt to+  create a value of type 'Dynamic' from a polymorphically-typed+  expression will result in an ambiguity error (see 'toDyn').++  'Show'ing a value of type 'Dynamic' returns a pretty-printed representation+  of the object\'s type; useful for debugging.+-}+data Dynamic =+    Dynamic TypeRep+            Obj++instance Show Dynamic+   -- the instance just prints the type representation.+                                                        where+    showsPrec _ (Dynamic t _) =+        showString "<<" . showsPrec 0 t . showString ">>"++-- here so that it isn't an orphan:+instance Exception Dynamic++type Obj = Any+ -- Use GHC's primitive 'Any' type to hold the dynamically typed value.+ --+ -- In GHC's new eval/apply execution model this type must not look+ -- like a data type.  If it did, GHC would use the constructor convention+ -- when evaluating it, and this will go wrong if the object is really a+ -- function.  Using Any forces GHC to use+ -- a fallback convention for evaluating it that works for all types.++-- | Converts an arbitrary value into an object of type 'Dynamic'.+--+-- The type of the object must be an instance of 'Typeable', which+-- ensures that only monomorphically-typed objects may be converted to+-- 'Dynamic'.  To convert a polymorphic object into 'Dynamic', give it+-- a monomorphic type signature.  For example:+--+-- >    toDyn (id :: Int -> Int)+--+toDyn :: Typeable a => a -> Dynamic+toDyn v = Dynamic (typeOf v) (unsafeCoerce v)++-- | Converts a 'Dynamic' object back into an ordinary Haskell value of+-- the correct type.  See also 'fromDynamic'.+fromDyn ::+       Typeable a+    => Dynamic -- ^ the dynamically-typed object+    -> a -- ^ a default value+    -> a -- ^ returns: the value of the first argument, if+                        -- it has the correct type, otherwise the value of+                        -- the second argument.+fromDyn (Dynamic t v) def+    | typeOf def == t = unsafeCoerce v+    | otherwise = def++-- | Converts a 'Dynamic' object back into an ordinary Haskell value of+-- the correct type.  See also 'fromDyn'.+fromDynamic ::+       Typeable a+    => Dynamic -- ^ the dynamically-typed object+    -> Maybe a -- ^ returns: @'Just' a@, if the dynamically-typed+                        -- object has the correct type (and @a@ is its value),+                        -- or 'Nothing' otherwise.+fromDynamic (Dynamic t v) =+    case unsafeCoerce v of+        r+            | t == typeOf r -> Just r+            | otherwise -> Nothing++-- (f::(a->b)) `dynApply` (x::a) = (f a)::b+dynApply :: Dynamic -> Dynamic -> Maybe Dynamic+dynApply (Dynamic t1 f) (Dynamic t2 x) =+    case funResultTy t1 t2 of+        Just t3 -> Just (Dynamic t3 ((unsafeCoerce f) x))+        Nothing -> Nothing++dynApp :: Dynamic -> Dynamic -> Dynamic+dynApp f x =+    case dynApply f x of+        Just r -> r+        Nothing ->+            errorWithoutStackTrace+                ("Type error in dynamic application.\n" +++                 "Can't apply function " ++ show f ++ " to argument " ++ show x)+#if !MIN_VERSION_base(4,9,0)+errorWithoutStackTrace = error+#endif+dynTypeRep :: Dynamic -> TypeRep+dynTypeRep (Dynamic tr _) = tr++data TypeCastException =+    TypeCastException TypeRep+                      TypeRep+    deriving (Typeable)++instance Show TypeCastException where+    show (TypeCastException expected recieved) =+        "TypeCastexception: Expected " +++        show expected ++ " got " ++ show recieved++instance Exception TypeCastException++-- | Coerce a dynamic to a value.+-- If the expected type is not the one inside the 'Dynamic' it throws an error showing both types.+forceDynamic ::+       forall a. Typeable a+    => Dynamic+    -> a+forceDynamic dyn+    | Just a <- fromDynamic dyn = a+    | otherwise = throw $ TypeCastException rep (dynTypeRep dyn)+  where+    rep = typeRep (Proxy :: Proxy a)
+ src/Data/StateElement.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE InstanceSigs #-}++module Data.StateElement where++import Data.Dynamic2++import Control.DeepSeq++--+-- Support for heterogeneous lists.+--+data S =+    forall a. Typeable a =>+              S (a -> ())+                Dynamic++toS :: forall a. (Typeable a, NFData a)+    => a+    -> S+toS a = S rnf' (toDyn a)+  where+    rnf' :: a -> ()+    rnf' = rnf++fromS :: Typeable a => S -> a+fromS (S _ a) = forceDynamic a++instance NFData S where+    rnf :: S -> ()+    rnf (S toRnf d) = toRnf $ forceDynamic d
+ src/Type/Magic.hs view
@@ -0,0 +1,13 @@+{-# LANGUAGE CPP #-}++module Type.Magic+    ( injectList+    , extractList+    , extractFunctor+    , injectFunctor+    ) where+#if MIN_VERSION_base(4,10,0)+import Type.Magic.GHC8 as X+#else+import Type.Magic.OldGHC as X+#endif
+ src/Type/Magic/GHC8.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE GADTs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE TypeApplications #-}++module Type.Magic.GHC8+    ( injectList+    , extractFunctor+    , extractList+    , injectFunctor+    ) where++import Control.Exception+import Data.Dynamic2 (Dynamic(..), TypeCastException(..))+import Data.Kind+import Type.Reflection+import Unsafe.Coerce++extractFunctor ::+       forall f. (Typeable f, Functor f)+    => Dynamic+    -> f Dynamic+extractFunctor =+    \case+        Dynamic (SomeTypeRep (App con tra)) dl ->+            case con `eqTypeRep` targetRep of+                Just HRefl -> fmap f $ unsafeCoerce dl+                Nothing ->+                    throw $+                    TypeCastException (SomeTypeRep con) (SomeTypeRep targetRep)+            where f = Dynamic (SomeTypeRep tra)+        Dynamic tr _ -> throw $ TypeCastException tr (SomeTypeRep targetRep)+  where+    targetRep = typeRep @f++extractList :: Dynamic -> [Dynamic]+extractList = extractFunctor++injectFunctor ::+       forall f. (Typeable f, Functor f)+    => Dynamic+    -> f Dynamic+    -> Dynamic+injectFunctor (Dynamic (SomeTypeRep tra) _) l =+    Dynamic tr $ unsafeCoerce $ fmap unwrap l+  where+    unwrap (Dynamic _ v) = v+    tr =+        case traKind `eqTypeRep` kindStar of+            Just HRefl -> SomeTypeRep $ App (typeRep @f) tra+            Nothing ->+                throw $+                TypeCastException (SomeTypeRep traKind) (SomeTypeRep kindStar)+      where+        traKind = typeRepKind tra+        kindStar = typeRep @Type++injectList :: [Dynamic] -> Dynamic+injectList [] = error "cannot convert empty list"+injectList l@(x:_) = injectFunctor x l
+ src/Type/Magic/OldGHC.hs view
@@ -0,0 +1,52 @@+module Type.Magic.OldGHC+    ( extractList+    , injectList+    , extractFunctor+    , injectFunctor+    ) where++import Control.Exception+import Data.Dynamic2 (Dynamic(..), TypeCastException(..))+import Data.Typeable+import Unsafe.Coerce++extractFunctor ::+       forall f. (Typeable f, Functor f)+    => Dynamic+    -> f Dynamic+extractFunctor =+    \(Dynamic trl dl) ->+        let (tyCon, tyArgs) = splitTyConApp trl+         in if tyCon == expectedTyCon+                then case tyArgs of+                         [] ->+                             error "Constructor must be at least of kind * -> *"+                         types -> fmap f l+                             where f = Dynamic $ last types+                                   l = unsafeCoerce dl+                         _ ->+                             error $ "Wrong kind for constructor " ++ show tyCon+                else throw $ TypeCastException expectedTy (mkTyConApp tyCon [])+  where+    !expectedTy = typeRep (undefined :: Proxy f)+    !expectedTyCon = typeRepTyCon expectedTy++extractList :: Dynamic -> [Dynamic]+extractList = extractFunctor++injectFunctor ::+       forall f. (Typeable f, Functor f)+    => Dynamic+    -> f Dynamic+    -> Dynamic+injectFunctor =+    \(Dynamic tra _) l -> Dynamic (mkTy tra) $ unsafeCoerce $ fmap unwrap l+  where+    unwrap (Dynamic _ v) = v+    !targetTyRep = typeRep (undefined :: Proxy f)+    !(!con, !args) = splitTyConApp targetTyRep+    mkTy tra = mkTyConApp con (args ++ [tra])++injectList :: [Dynamic] -> Dynamic+injectList [] = error "Cannot convert empty list yet"+injectList l@(x:_) = injectFunctor x l
+ stc-lang.cabal view
@@ -0,0 +1,140 @@+cabal-version: 1.12+name: stc-lang+version: 1.0.0+license: BSD3+license-file: LICENSE+copyright: 2017-2019 Norman Rink, Sebastian Ertel, Justus Adam+maintainer: sebastian.ertel@tu-dresden.de+author: Norman Rink, Sebastian Ertel, Justus Adam+homepage: https://github.com/ohua-dev/stc-lang#readme+synopsis: A library for implicit, monadic dataflow parallelism+description:+    See the <https://github.com/ohua-dev/stc-lang#readme readme>+category: Concurrency, Development+build-type: Simple+extra-source-files:+    README.md++source-repository head+    type: git+    location: https://github.com/ohua-dev/stc-lang++flag debug-sched+    description:+        Enable the debuggable scheduler+    default: False+    manual: True++library+    exposed-modules:+        Control.Monad.SD+        Data.Dynamic2+        Data.StateElement+        Type.Magic+        Control.Monad.Generator+        Control.Monad.Stream+        Control.Monad.Stream.Chan+        Control.Monad.Stream.Par+        Control.Monad.Stream.PinnedChan+    hs-source-dirs: src+    other-modules:+        Control.Monad.SD.Case+        Control.Monad.SD.Combinator+        Control.Monad.SD.FRP+        Control.Monad.SD.Ohua+        Control.Monad.SD.STCLang+        Control.Monad.SD.Smap+    default-language: Haskell2010+    default-extensions: DeriveGeneric ExistentialQuantification+                        ExplicitForAll FlexibleContexts FlexibleInstances+                        ScopedTypeVariables TupleSections LambdaCase RankNTypes+                        NamedFieldPuns MultiParamTypeClasses RecordWildCards+                        TypeSynonymInstances BangPatterns DeriveFunctor RecordWildCards+    ghc-options: -Wall -O2 -fPIC -fno-cse+    build-depends:+        BoundedChan >=1.0.3.0,+        abstract-par >=0.3.3,+        base >=4.7 && <5,+        bytestring >=0.10.8.2,+        deepseq >=1.4.3.0,+        microlens >=0.4.8.3,+        monad-par >=0.3.4.8,+        monad-par-extras >=0.3.3,+        mtl >=2.2.1,+        transformers >=0.5.2.0+    +    if flag(debug-sched)+        cpp-options: -DDEBUG_SCHED+    +    if impl(ghc >=8.0.0)+        other-modules:+            Type.Magic.GHC8+    else+        other-modules:+            Type.Magic.OldGHC++executable ohua-stream-bench+    main-is: algo.hs+    hs-source-dirs: stream-bench+    other-modules:+        CampaignProcMap+        MutableNFMap+        MutableSet+        Paths_stc_lang+    default-language: Haskell2010+    default-extensions: DeriveGeneric ExistentialQuantification+                        ExplicitForAll FlexibleContexts FlexibleInstances+                        ScopedTypeVariables TupleSections LambdaCase RankNTypes+                        NamedFieldPuns MultiParamTypeClasses RecordWildCards+                        TypeSynonymInstances BangPatterns DeriveFunctor RecordWildCards+    ghc-options: -Wall -O2 -threaded -with-rtsopts=-N+    build-depends:+        BoundedChan >=1.0.3.0,+        aeson >=1.2.3.0,+        base >=4.7 && <5,+        bytestring >=0.10.8.2,+        clock >=0.7.2,+        deepseq >=1.4.3.0,+        hashable >=1.2.6.1,+        hashtables >=1.2.2.1,+        hedis >=0.9.12,+        hw-kafka-client >=2.6.0,+        microlens >=0.4.8.3,+        microlens-aeson >=2.2.0.2,+        mtl >=2.2.1,+        random >=1.1,+        stc-lang -any,+        text >=1.2.2.2,+        transformers >=0.5.2.0,+        uuid-types >=1.0.3,+        vector >=0.12.0.1,+        yaml >=0.8.28++test-suite statefulness-test+    type: exitcode-stdio-1.0+    main-is: Spec.hs+    hs-source-dirs: test+    other-modules:+        FakeComputation+        SD.Correctness+        SD.Performance+        Paths_stc_lang+    default-language: Haskell2010+    default-extensions: DeriveGeneric ExistentialQuantification+                        ExplicitForAll FlexibleContexts FlexibleInstances+                        ScopedTypeVariables TupleSections LambdaCase RankNTypes+                        NamedFieldPuns MultiParamTypeClasses RecordWildCards+                        TypeSynonymInstances BangPatterns DeriveFunctor RecordWildCards+    ghc-options: -Wall -O2 -threaded -rtsopts -with-rtsopts=-N4+    build-depends:+        HUnit >=1.6.0.0,+        base >=4.7 && <5,+        deepseq >=1.4.3.0,+        ghc-prim >=0.5.1.1,+        microlens >=0.4.8.3,+        mtl >=2.2.1,+        stc-lang -any,+        test-framework >=0.8.1.1,+        test-framework-hunit >=0.3.0.2,+        time >=1.8.0.2,+        transformers >=0.5.2.0
+ stream-bench/CampaignProcMap.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE ConstraintKinds #-}++module CampaignProcMap+    ( Map+    , new+    , insert+    , mapM_+    ) where++import Control.DeepSeq+import Control.Monad.IO.Class+import qualified Data.HashTable.IO as MHT+import Data.Hashable (Hashable)+import qualified MutableSet as Set+import Prelude hiding (mapM_)++type Constraint a = (Hashable a, Eq a)++newtype Map k v = Map+    { unwrap :: MHT.BasicHashTable k (Set.Set v)+    }++-- This may seem like an odd instance for NFData, but as with the `HashSet` type+-- my reasoning is that 'HashTable' is strict in the keys, thus we don't need to+-- force them. And since the values are 'HashSet', which is also completely+-- strict already we don't need to specially evaluate it.+instance NFData (Map k v) where+    rnf _ = ()++new :: IO (Map k v)+new = Map <$> MHT.new++insert ::+       (Constraint k, Set.Constraint v, MonadIO m) => k -> v -> Map k v -> m ()+insert k v m =+    liftIO $ do+        set <- maybe Set.new pure =<< MHT.lookup (unwrap m) k+        Set.insert v set++mapM_ :: MonadIO m => ((k, Set.Set v) -> IO a) -> Map k v -> m ()+mapM_ f = liftIO . MHT.mapM_ f . unwrap
+ stream-bench/MutableNFMap.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE ConstraintKinds #-}++module MutableNFMap+    ( Map+    , new+    , insert+    , delete+    , lookup+    , mapM_+    , size+    ) where++import Control.DeepSeq+import Control.Monad.IO.Class+import qualified Data.HashTable.IO as MHT+import Data.Hashable (Hashable)+import Prelude hiding (lookup, mapM_)++type Constraint a = (Hashable a, Eq a)++newtype Map k v = Map+    { unwrap :: MHT.BasicHashTable k v+    }++-- | This instance does nothing, because the functions exposed force the keys+-- and values automatically+instance NFData (Map k v) where+    rnf _ = ()++new :: MonadIO m => m (Map k v)+new = liftIO $ Map <$> MHT.new++-- | I only require an 'NFData' instance for the values, because the assumption+-- is that calculating the hash for the keys will force them.+insert :: (Constraint k, NFData v, MonadIO m) => k -> v -> Map k v -> m ()+insert k v m = liftIO $ v `deepseq` MHT.insert (unwrap m) k v++delete :: (Constraint k, MonadIO m) => k -> Map k v -> m ()+delete k m = liftIO $ MHT.delete (unwrap m) k++lookup :: (Constraint k, MonadIO m) => k -> Map k v -> m (Maybe v)+lookup k m = liftIO $ MHT.lookup (unwrap m) k++mapM_ :: MonadIO m => ((k, v) -> IO a) -> Map k v -> m ()+mapM_ f = liftIO . MHT.mapM_ f . unwrap++size :: MonadIO m => Map k v -> m Word+size = liftIO . MHT.foldM (\a _ -> pure $ a + 1) 0 . unwrap
+ stream-bench/MutableSet.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE ConstraintKinds #-}++module MutableSet+    ( Set+    , Constraint+    , new+    , delete+    , insert+    , member+    , mapM_+    , size+    , toList+    ) where++import Control.DeepSeq+import Control.Monad.IO.Class (MonadIO, liftIO)+import qualified Data.HashTable.IO as HT+import Data.Hashable (Hashable)+import Data.Maybe (isJust)+import Prelude hiding (mapM_)++type HashSetInner a = HT.BasicHashTable a ()++newtype Set a = Set+    { unwrap :: HashSetInner a+    }++type Constraint a = (Hashable a, Eq a)++new :: MonadIO m => m (Set a)+new = liftIO $ Set <$> HT.new++insert :: (MonadIO m, Constraint a) => a -> Set a -> m ()+insert item t = liftIO $ HT.insert (unwrap t) item ()++delete :: (MonadIO m, Constraint a) => a -> Set a -> m ()+delete item set = liftIO $ HT.delete (unwrap set) item++member :: (MonadIO m, Constraint a) => a -> Set a -> m Bool+member i t = liftIO $ isJust <$> HT.lookup (unwrap t) i++mapM_ :: MonadIO m => (a -> IO b) -> Set a -> m ()+mapM_ f = liftIO . HT.mapM_ (f . fst) . unwrap++size :: MonadIO m => Set a -> m Word+size = liftIO . HT.foldM (\a _ -> pure $ a + 1) 0 . unwrap++toList :: (MonadIO m, Constraint a) => Set a -> m [a]+toList = liftIO . fmap (map fst) . HT.toList . unwrap++-- This is a weird NFData instance, but the assumption is that HashMaps are+-- strict in the keys, because computing the hash forces the key. And since a+-- set does not have non-unit values, the values need not be forced.+instance NFData (Set i) where+    rnf _ = ()
+ stream-bench/algo.hs view
@@ -0,0 +1,803 @@+{-# LANGUAGE ConstraintKinds, TypeApplications, OverloadedStrings,+  PartialTypeSignatures, OverloadedLists, TypeFamilies, MultiWayIf+  #-}++import Control.Concurrent (forkIO, killThread, newEmptyMVar, threadDelay)+import qualified Control.Concurrent as Conc+import qualified Control.Concurrent.BoundedChan as BC+import Control.Concurrent.MVar (putMVar, takeMVar)+import Control.DeepSeq (NFData, deepseq)+import Control.Exception (assert, bracket)+import Control.Monad+    ( (<=<)+    , (>=>)+    , forM+    , forM_+    , forever+    , join+    , replicateM+    , unless+    , void+    , when+    )+import Control.Monad.Generator (Generator, foldlGeneratorT, ioReaderToGenerator)+import Control.Monad.IO.Class (MonadIO(liftIO))+import Control.Monad.List (ListT(ListT), runListT)+import Control.Monad.Reader (ReaderT, ask, lift, runReaderT)+import Control.Monad.SD+import Control.Monad.State.Class as S+    ( MonadState+    , get+    , gets+    , modify+    , put+    , state+    )+import Control.Monad.State.Lazy as S (StateT, runStateT)+import Data.Aeson ((.:), decode)+import qualified Data.Aeson as AE+import qualified Data.Aeson.Types as AE+import Data.Aeson.Types (parseMaybe)+import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy as LBS+import Data.Hashable+import Data.IORef (IORef, modifyIORef', newIORef, readIORef, writeIORef)+import Data.Int (Int64)+import Data.List (nub)+import Data.Maybe (fromJust, fromMaybe, isNothing)+import Data.Monoid ((<>))+import Data.StateElement+import Data.String (IsString)+import Data.Text (Text)+import qualified Data.Text as Tx+import qualified Data.Text.Encoding as Tx+import qualified Data.Text.IO as Tx+import Data.Typeable (Typeable)+import qualified Data.UUID.Types as UUID+import qualified Data.Vector as V+import qualified Data.Vector.Mutable as MV+import qualified Data.Vector.Unboxed as UV+import qualified Data.Vector.Unboxed.Mutable as UMV+import Data.Word (Word32)+import qualified Data.Yaml as Yaml+import qualified Debug.Trace as Debug+import GHC.Exts (IsList, Item)+import GHC.Generics (Generic)+import Lens.Micro+import Lens.Micro.Aeson+import qualified MutableNFMap as NFMap+import qualified MutableSet as Set+import Prelude hiding (String, show)+import qualified Prelude as P+import qualified System.Clock as Clock+import System.Environment+import System.IO (Handle, IOMode(WriteMode), withFile)+import System.IO.Unsafe (unsafePerformIO)+import System.Random (randomIO)+import System.Random (randomIO, randomRIO)+import Text.Printf (hPrintf, printf)++import qualified Database.Redis as Redis++import qualified Kafka.Consumer as K++type String = BS.ByteString++type Message = LBS.ByteString++type Long = Int64++data Window = Window+    { seenCount :: IORef Long+    , timestamp :: Text+    } deriving (Eq, Generic)++instance Hashable Window where+    hashWithSalt s w = hashWithSalt s (timestamp w)++instance NFData Window++instance AE.FromJSON String where+    parseJSON = AE.withText "Expected String" $ pure . Tx.encodeUtf8++data Collector a = Collector+    { counts :: IORef (UMV.IOVector a)+    , lastIndex :: IORef Int+    }++collectorInitialSize :: Int+collectorInitialSize = 400++newCollector :: (MonadIO m, UMV.Unbox a) => m (Collector a)+newCollector =+    liftIO $ Collector <$> (newIORef =<< UMV.unsafeNew collectorInitialSize) <*>+    newIORef 0++push :: (MonadIO m, UMV.Unbox a) => Collector a -> a -> m ()+push Collector {..} i =+    liftIO $ do+        indx <- readIORef lastIndex+        c <- readIORef counts+        let l = UMV.length c+        assert (indx <= l && indx > 0) (pure ())+        c' <-+            if l == indx+                then do+                    new <- UMV.unsafeGrow c l+                    writeIORef counts new+                    pure new+                else pure c+        UMV.write c' indx i+        modifyIORef' lastIndex succ++unsafeRead :: (MonadIO m, UMV.Unbox a) => Collector a -> m (UV.Vector a)+unsafeRead Collector {..} =+    liftIO $ do+        idx <- readIORef lastIndex+        UV.unsafeFreeze . UMV.slice 0 idx =<< readIORef counts++data Statistics = Statistics+    { fetcherEventCount :: Collector Int+    , pipelineEventCount :: Collector Word32+    , redisWritesCount :: Collector Int+    }++initStatistics :: IO Statistics+initStatistics = Statistics <$> newCollector <*> newCollector <*> newCollector++writeStatistics :: MonadIO m => Statistics -> Handle -> m ()+writeStatistics Statistics {..} h =+    liftIO $ do+        hPrintf+            h+            "%15s %15s %15s\n"+            ("Fetcher Events" :: P.String)+            ("Pipeline Events" :: P.String)+            ("Write events" :: P.String)+        fec <- unsafeRead fetcherEventCount+        pec <- unsafeRead pipelineEventCount+        rwc <- unsafeRead redisWritesCount+        UV.forM_ (UV.zip3 fec pec rwc) $ \(fcount, pcount, rcount) ->+            hPrintf h "%-15d %-15d %-15d\n" fcount pcount rcount+        let l1 = UV.length fec+            l2 = UV.length pec+            l3 = UV.length rwc+        unless (l1 == l2 && l2 == l3) $+            printf "Lengths were unequal: fec=%7d pec=%7d rwc=%7d\n" l1 l2 l3++fromRight :: Show a => Either a b -> b+fromRight = either (error . P.show) id++show :: Show a => a -> Text+show = Tx.pack . P.show++timeDivisor :: Long+timeDivisor = 10 * 1000++-- | Timeout for the timer trigger. In the actual benchmarks this is one second.+-- I choose 10 seconds here because in one second only very little happens+timeout :: Int+timeout = 1000 * 1000 * 10++eventGenerationStep = 100 * 1000++kafkaEventCount = 10 * 1000 * 1000++numCampaigns = 100++currentMilliSecs :: IO Long+currentMilliSecs =+    (`div` (1000 * 1000)) . fromIntegral . Clock.toNanoSecs <$>+    Clock.getTime Clock.Realtime++-- | Do this for `kafkaEventCount` many times+generateKafkaEvents :: V.Vector Text -> IO [AE.Value]+generateKafkaEvents ads = do+    startTime <-+        fromIntegral . (* eventGenerationStep) . Clock.toNanoSecs <$>+        Clock.getTime Clock.Realtime+    runListT $ do+        n <- ListT $ pure [0 .. 10000 :: Word]+        userId <- liftIO $ randomIO @UUID.UUID+        pageId <- liftIO $ randomIO @UUID.UUID+        ad <- randomIn ads+        adType <- randomIn adTypes+        eventType <- randomIn eventTypes+        pure $+            AE.object+                [ "user_id" AE..= userId+                , "page_id" AE..= pageId+                , "ad_id" AE..= ad+                , "ad_type" AE..= adType+                , "event_type" AE..= eventType+                , "event_time" AE..= (startTime + (n * 10) + skew + lateBy)+                , "ip_address" AE..= ("1.2.3.4" :: Text)+                ]+  where+    adTypes =+        V.fromList+            ["banner", "modal", "sponsored-search", "mail", "mobile" :: Text]+    eventTypes = V.fromList ["view", "click", "purchase" :: Text]+    skew = 0+    lateBy = 0+    randomIn l = (l V.!) <$> liftIO (randomRIO (0, V.length l - 1))++eventGenerationLoop :: ([Message] -> IO ()) -> IO ()+eventGenerationLoop writer = do+    ads <-+        V.fromList <$> replicateM (10 * numCampaigns) (UUID.toText <$> randomIO)+    --ads <- Tx.lines <$> Tx.readFile "ad-ids.txt"+    forM_ @[] @IO @Word [0,fromIntegral eventGenerationStep .. kafkaEventCount] $ \_ -> do+        evs <- generateKafkaEvents ads+        let evaluated = map AE.encode evs+        evaluated `deepseq` pure ()+        putStrLn "Writing Events"+        writer evaluated++-- NOTE I guessed this number. I have not seen any timeout specification in the+-- benchmark. This is in milliseconds.+kafkaTimeout :: K.Timeout+kafkaTimeout = K.Timeout 4000++-- NOTE I also guessed this number. No idea whether this value is appropriate.+kafkaBatchSize :: K.BatchSize+kafkaBatchSize = K.BatchSize 400++readKafka :: K.KafkaConsumer -> Statistics -> IO [Message]+readKafka con stats = do+    subsState <- fromRight <$> K.subscription con+    --putStrLn $ "Subscription state: \n" <> P.show subsState+    resps <- K.pollMessageBatch con kafkaTimeout (K.BatchSize batchSize)+    oldCount <- readIORef msgsRead+    time <- currentMilliSecs+    oldTime <- readIORef timeFrame+    let msgs =+            map+                (LBS.fromStrict . fromJust . K.crValue .+                 (\a -> assert (isNothing $ K.crKey a) a) .+                 fromRight)+                resps+    let !newCount = oldCount + length msgs+    if (time - oldTime > 10000)+        then do+            writeIORef timeFrame time+            writeIORef msgsRead 0+            putStrLn $ "Fetched " <> P.show newCount <>+                " messages in the last ~10 seconds"+            push (fetcherEventCount stats) newCount+        else writeIORef msgsRead newCount+               -- I just put this assert in here for now so that we can reason+               -- better about the structure of the kafka message+    --msgs `deepseq` putStrLn "done"+    pure msgs+  where+    {-# NOINLINE msgsRead #-}+    {-# NOINLINE timeFrame #-}+    msgsRead = unsafePerformIO $ newIORef 0+    timeFrame = unsafePerformIO $ newIORef =<< currentMilliSecs+    batchSize = 400++getIndex :: Int -> [a] -> Maybe a+getIndex n _+    | n < 0 = Nothing+getIndex _ [] = Nothing+getIndex 0 (x:_) = Just x+getIndex n (_:xs) = getIndex (n - 1) xs++writeRedis :: _ -> _ -> Redis.Redis _+writeRedis campaign window = do+    redisResponse <- getUUID (Tx.encodeUtf8 $ timestamp window)+    windowUUID <-+        case redisResponse of+            Nothing -> do+                windowUUID <- encodeUUID <$> liftIO randomIO+                checkR_ $+                    Redis.hset+                        campaign+                        (Tx.encodeUtf8 $ timestamp window)+                        windowUUID+                redisResponse2 <- getUUID "windows"+                windowListUUID <-+                    case redisResponse2 of+                        Nothing -> do+                            rand <- encodeUUID <$> liftIO randomIO+                            checkR_ $ Redis.hset campaign "windows" rand+                            pure rand+                        Just uuid -> pure uuid+                checkR_ $+                    Redis.lpush+                        windowListUUID+                        [Tx.encodeUtf8 $ timestamp window]+                pure windowUUID+            Just uuid -> pure uuid+    checkR_ $ Redis.hincrby windowUUID "seen_count" . fromIntegral =<<+        liftIO (readIORef (seenCount window))+    liftIO $ writeIORef (seenCount window) 0+    time <- BS.pack . P.show <$> liftIO currentMilliSecs+    checkR_ $ Redis.hset windowUUID "time_updated" time+    Redis.lpush "time_updated" [time]+    -- NOTE This function is not necessary. It "only" forces the errors from the+    -- redis database. You can drop it and all its uses if you want to describe+    -- the algorithm. The reason I have it is so we notice if something goes+    -- wrong with the redis database.+  where+    checkR_ :: (Monad f, Show err) => f (Either err a) -> f ()+    checkR_ = (either (error . P.show) (const $ pure ()) =<<)+    getUUID field =+        either (const Nothing) (join . getIndex 0) <$>+        Redis.hmget campaign [field]+    encodeUUID = BS.pack . P.show :: UUID.UUID -> BS.ByteString++redisGet :: MonadIO m => _ -> BS.ByteString -> m (Maybe BS.ByteString)+redisGet redisConn =+    liftIO . Redis.runRedis redisConn . fmap fromRight . Redis.get++isTheSame :: (Show a, Typeable a, Eq a, NFData a) => IO a -> STCLang a Bool+isTheSame init =+    liftWithState init $ \new -> do+        old <- get+        let isOld = old == new+        unless isOld $ put new+        pure $ isOld++redisJoinStateInit :: IO (NFMap.Map _ _)+redisJoinStateInit = NFMap.new++newWindow :: MonadIO m => Long -> m Window+newWindow timeBucket =+    liftIO $ Window <$> newIORef 0 <*> pure (show $ timeBucket * timeDivisor)++-- NOTE This function doesn't do much. I did make an extra function for this+-- because I am not sure why they changed this function to be so simple, and why+-- they removed the call to redis that I assume was in here. I have a suspicion,+-- that the simplification here means that this benchmark is not as it was+-- described in the paper and because of that I leave the function in so we+-- remember to check it later.+redisGetWindow :: MonadIO m => Long -> m (Maybe Window)+redisGetWindow timeBucket = Just <$> newWindow timeBucket++getWindow ::+       (MonadIO m, MonadState (w, NFMap.Map Long (NFMap.Map String Window)) m)+    => Long+    -> String+    -> m Window+getWindow timeBucket campaignId = do+    campaignWindows <- gets snd+    bucketMapE <-+        NFMap.lookup timeBucket campaignWindows >>= \case+            Just m -> pure $ Left m+            Nothing ->+                redisGetWindow timeBucket >>= \case+                    Nothing -> do+                        m <- NFMap.new+                        NFMap.insert timeBucket m campaignWindows+                        pure $ Left m+                    Just redisWindow -> do+                        m <- NFMap.new+                        NFMap.insert timeBucket m campaignWindows+                        pure $ Right redisWindow+    case bucketMapE of+        Right w -> pure w+        Left bucketMap ->+            NFMap.lookup campaignId bucketMap >>= \case+                Nothing -> do+                    window <-+                        maybe (newWindow timeBucket) pure =<<+                        redisGetWindow timeBucket+                    NFMap.insert campaignId window bucketMap+                    pure window+                Just window -> pure window++-- | These are necessary because the Kafka client is an older version (0.8.2.1)+-- and does not support the `ApiVersionRequest` that the C-client library we use+-- under the hood sends in the beginning.+extraKafkaProperties ::+       (IsList l, Item l ~ (s0, s1), IsString s1, IsString s0) => l+extraKafkaProperties =+    [("api.version.request", "false"), ("broker.version.fallback", "0.8.2.1")]++type KafkaReader = IO LBS.ByteString++type CloseKafka = IO ()++type KafkaActions = (KafkaReader, CloseKafka)++cachedBackoffReader :: _ -> IO [a] -> IO (IO a)+cachedBackoffReader backoff refetch = do+    cache <- newIORef []+    let go =+            readIORef cache >>= \case+                [] -> do+                    new <- refetch+                    if null new+                        then putStrLn "Refetch returned empty response" >>+                             threadDelay backoff+                        else writeIORef cache new+                    go+                (x:xs) -> writeIORef cache xs >> pure x+    pure go++setupMockKafka :: _ -> IO KafkaActions+setupMockKafka _conf = do+    kafkaVar <- newEmptyMVar+    let kafkaWriter m = putMVar kafkaVar m+    kafkaReader <- cachedBackoffReader 100 (takeMVar kafkaVar)+    writerThread <- forkIO $ eventGenerationLoop kafkaWriter+    pure (kafkaReader, killThread writerThread)++setupKafka :: Statistics -> AE.Value -> IO KafkaActions+setupKafka stats conf = do+    print topic+    print conf+    print $ K.cpProps props+    cons <- fromRight <$> (K.newConsumer props sub)+    reader <- cachedBackoffReader 100 $ readKafka cons stats+    pure (reader, maybe (pure ()) (error . P.show) =<< K.closeConsumer cons)+  where+    topic = K.TopicName $ conf ^?! key "kafka.topic" . _String+    sub = K.topics [topic] <> K.offsetReset K.Latest+    props+            -- NOTE If I do not assign a group it fails immediately with+            -- "unknown group".+     =+        K.groupId (K.ConsumerGroupId "ohua-stream-bench-group") <>+        --K.extraProps extraKafkaProperties <>+        --K.debugOptions [K.DebugAll] <>+        K.brokersList+            (map (\host ->+                      K.BrokerAddress $ host <> ":" <>+                      show (conf ^?! key "kafka.port" . _Integer))+                 (conf ^.. key "kafka.brokers" . values . _String))++-- | Reads the config file at the specified path and creates the connection+-- objects we need+setup :: FilePath -> IO (KafkaActions, Redis.Connection, Statistics)+setup loc = do+    conf <- fromRight <$> Yaml.decodeFileEither @AE.Value loc+    let rinfo =+            Redis.defaultConnectInfo+                { Redis.connectHost =+                      conf ^?! key "redis.host" . _String . to Tx.unpack+                }+    -- cons <- fmap fromRight (K.newConsumer props sub)+    stats <- initStatistics+    (,,) <$> setupKafka stats conf <*> Redis.checkedConnect rinfo <*> pure stats++withInitial msg ac = do+    putStrLn $ "Doing initial " <> msg <> "..."+    r <- ac+    putStrLn $ msg <> " done"+    pure r++traceM :: Monad m => P.String -> m ()+traceM msg = Debug.trace msg $ pure ()++-- NOTE in the original implementation of the algorithm `rebalance` is the first+-- function called on the input stream. This distributes the messages+-- round-robin. This means we could also spawn multiple algorithm instances and+-- process multiple messages in parallel. But we'd have to ensure the timer+-- events are *not* round robin distributed!+algo kafkaReader redisConn stats+    -- Allocate the basic functions --+ = do+    traceM "Start allocations"+    let deserialize o =+            either (error . ("Decoding error: " <>)) id $ do+                result <- AE.eitherDecode o+                flip AE.parseEither result $ \o ->+                    (,,,,,,) <$> (o .: "user_id" :: _ String) <*>+                    (o .: "page_id" :: _ String) <*>+                    (o .: "ad_id" :: _ String) <*>+                    (o .: "ad_type" :: _ String) <*>+                    (o .: "event_type" :: _ String) <*>+                    (read <$> o .: "event_time") <*>+                    (o .: "ip_address" :: _ String)+    let evFilterFunc ~(_, _, _, _, t, _, _) = pure $ t == "view"+    let project ~(_, _, i2, _, _, i5, _) = pure (i2, i5)+    traceM "Allocating redis"+    redisJoin <-+        liftWithState redisJoinStateInit $ \(adId, v2) ->+            fmap (, adId, v2) <$> do+                st <- get+                NFMap.lookup adId st >>= \case+                    Just cid -> pure $ Just cid+                    Nothing -> do+                        mcid <- redisGet redisConn adId+                        maybe+                            (liftIO $ putStrLn "Ad campaign not found in redis")+                            (\cid -> NFMap.insert adId cid st)+                            mcid+                        pure mcid+    -- The campaign processor wrapped in the logic to separate handling of the+    -- timing event, regular and filtered data.+    emitCounter <-+        liftWithState (pure 0 :: _ Word32) $ \case+            Right _ -> modify succ+            Left _ -> do+                c <- get+                put 0+                liftIO $ putStrLn $ "Saw " <> P.show c <>+                    " events total in time window"+                push (pipelineEventCount stats) c+    traceM "Allocating campaign"+    processCampaign <-+        liftWithState ((,) <$> Set.new <*> NFMap.new) $ \case+            Right (Just ev@(campaignId, _adId, eventTime)) -> do+                flushCache <- gets fst+                let timeBucket = eventTime `div` timeDivisor+                window <- getWindow timeBucket campaignId+                liftIO $ modifyIORef' (seenCount window) (+ 1)+                let value = (campaignId, window)+                Set.insert value flushCache+            Left timerTrigger -> do+                (s, cache) <- get+                newCache <- Set.new+                modify (\(_, o) -> (newCache, o))+                asList <- Set.toList s+                --liftIO $ printf "I touched %d campaigns" (length $ nub $ map fst asList)+                let l = length asList+                push (redisWritesCount stats) l+                liftIO $ do+                    putStrLn $ "Initiated redis write of " <> P.show l <>+                        " events"+                    Redis.runRedis redisConn $+                        mapM_+                            (\(cid, window) -> do+                                 c <- liftIO $ readIORef $ seenCount window+                                   --liftIO $ printf "Writing count %4d for timestamp %v\n" c (timestamp window)+                                 writeRedis cid window)+                            asList+            _ -> pure ()+    -- The condition we will use later for the if+    evCheck <- isTheSame currentMilliSecs+    -- Allocate signals+    traceM "Allocating timer"+    timerSig <-+        liftSignal+            (threadDelay timeout >> currentMilliSecs)+            (withInitial "time" currentMilliSecs)+    -- Not sure if this is a good idea but I initialize here by polling the+    -- first message. Perhaps we should use a `Maybe` instead, however its not+    -- particularly convenient yet in our model so I do this.+    traceM "Allocating Kafka reader"+    msgSig <- liftSignal kafkaReader (withInitial "read kafka" kafkaReader)+    traceM "Allocating preprocessor"+    filteredProcessor+        -- NOTE keyBy partitions the operator state according to some key. If we+        -- have time we should implement that too+         <-+        filterSignalM+            evFilterFunc+            (project >=> redisJoin+                                       -- >=> keyBy 0+             )+    -- The actual algorithm+    return $ \src -> do+        timerEv <- timerSig src+        msgEv <- msgSig src+        -- Fork on whether this is a timing event+        procInput <-+            if_+                (evCheck timerEv)+                (Right <$> do+                     msg <- pure $ deserialize msgEv+                     join <$> filteredProcessor msg)+                (pure $ Left timerEv)+        emitCounter procInput+        processCampaign procInput++-- data CollSt = CollSt+--   { states :: [S]+--   , signals :: [IO S]+--   }+--+-- instance Monoid CollSt where+--   mempty = CollSt [] []+--   CollSt st1 si1 `mappend` CollSt st2 si2 =+--     CollSt (st1 `mappend` st2) (si1 `mappend` si2)+algoSeq kafkaReader redisConn stats+    -- Allocate the basic functions --+ =+    runSignals $ do+        traceM "Start allocations"+        let deserialize o =+                either (error . ("Decoding error: " <>)) id $ do+                    result <- AE.eitherDecode o+                    flip AE.parseEither result $ \o ->+                        (,,,,,,) <$> (o .: "user_id" :: _ String) <*>+                        (o .: "page_id" :: _ String) <*>+                        (o .: "ad_id" :: _ String) <*>+                        (o .: "ad_type" :: _ String) <*>+                        (o .: "event_type" :: _ String) <*>+                        (read <$> o .: "event_time") <*>+                        (o .: "ip_address" :: _ String)+        let evFilterFunc ~(_, _, _, _, t, _, _) = pure $ t == "view"+        let project ~(_, _, i2, _, _, i5, _) = pure (i2, i5)+        traceM "Allocating redis"+        redisJoin <-+            liftWithState redisJoinStateInit $ \(adId, v2) ->+                fmap (, adId, v2) <$> do+                    st <- get+                    NFMap.lookup adId st >>= \case+                        Just cid -> pure $ Just cid+                        Nothing -> do+                            mcid <- redisGet redisConn adId+                            maybe+                                (liftIO $+                                 putStrLn "Ad campaign not found in redis")+                                (\cid -> NFMap.insert adId cid st)+                                mcid+                            pure mcid+    -- The campaign processor wrapped in the logic to separate handling of the+    -- timing event, regular and filtered data.+        emitCounter <-+            liftWithState (pure 0 :: _ Word32) $ \case+                Right _ -> modify succ+                Left _ -> do+                    c <- get+                    put 0+                    liftIO $ putStrLn $ "Saw " <> P.show c <>+                        " events total in time window"+                    push (pipelineEventCount stats) c+        traceM "Allocating campaign"+        processCampaign <-+            liftWithState ((,) <$> Set.new <*> NFMap.new) $ \case+                Right (Just ev@(campaignId, _adId, eventTime)) -> do+                    flushCache <- gets fst+                    let timeBucket = eventTime `div` timeDivisor+                    window <- getWindow timeBucket campaignId+                    liftIO $ modifyIORef' (seenCount window) (+ 1)+                    let value = (campaignId, window)+                    Set.insert value flushCache+                Left timerTrigger -> do+                    (s, cache) <- get+                    newCache <- Set.new+                    modify (\(_, o) -> (newCache, o))+                    asList <- Set.toList s+                --liftIO $ printf "I touched %d campaigns" (length $ nub $ map fst asList)+                    let l = length asList+                    push (redisWritesCount stats) l+                    liftIO $ do+                        putStrLn $ "Initiated redis write of " <> P.show l <>+                            " events"+                        Redis.runRedis redisConn $+                            mapM_+                                (\(cid, window) -> do+                                     c <- liftIO $ readIORef $ seenCount window+                                   --liftIO $ printf "Writing count %4d for timestamp %v\n" c (timestamp window)+                                     writeRedis cid window)+                                asList+                _ -> pure ()+    -- The condition we will use later for the if+        evCheck <- isTheSame currentMilliSecs+    -- Allocate signals+        traceM "Allocating timer"+        timerSig <-+            liftSignal+                (threadDelay timeout >> currentMilliSecs)+                (withInitial "time" currentMilliSecs)+    -- Not sure if this is a good idea but I initialize here by polling the+    -- first message. Perhaps we should use a `Maybe` instead, however its not+    -- particularly convenient yet in our model so I do this.+        traceM "Allocating Kafka reader"+        msgSig <- liftSignal kafkaReader (withInitial "read kafka" kafkaReader)+        traceM "Allocating preprocessor"+        filteredProcessor+        -- NOTE keyBy partitions the operator state according to some key. If we+        -- have time we should implement that too+             <-+            filterSignalM+                evFilterFunc+                (project >=> redisJoin+                                       -- >=> keyBy 0+                 )+    -- The actual algorithm+        pure $ \src -> do+            timerEv <- timerSig src+            msgEv <- msgSig src+        -- Fork on whether this is a timing event+            procInput <-+                if_+                    (evCheck timerEv)+                    (Right <$> do+                         msg <- pure $ deserialize msgEv+                         join <$> filteredProcessor msg)+                    (pure $ Left timerEv)+            emitCounter procInput+            processCampaign procInput+  where+    isTheSame init =+        liftWithState init $ \new -> do+            old <- get+            let isOld = old == new+            unless isOld $ put new+            pure $ isOld+    printSignalD = putStrLn+    runOhuaM comp states = do+        v <- V.thaw (V.fromList states)+        a <- runReaderT comp v+        (a, ) . V.toList <$> V.freeze v+    if_ c t e = do+        c' <- c+        if c'+            then t+            else e+    filterSignalM cond f =+        pure $ \item -> if_ (cond item) (Just <$> f item) (pure Nothing)+    liftWithState ::+           (Typeable s, NFData s)+        => IO s+        -> (SF s a b)+        -> BuildSeqOhua (a -> SeqOhua b)+    liftWithState state stateThread = do+        s0 <- lift state+        l <-+            S.state $ \s ->+                (length $ states s, s {states = states s ++ [toS s0]})+        pure $ \a -> do+            v <- ask+            liftIO $ do+                s <- fromS <$> MV.read v l+                (b, s') <- runStateT (stateThread a) s+                MV.write v l $ toS s'+                pure b+    liftSignal ::+           (Typeable a, NFData a)+        => IO a+        -> IO a+        -> BuildSeqOhua (Signals -> SeqOhua a)+    liftSignal s0 init = do+        idx <-+            S.state $ \s@CollSt {signals} ->+                (length signals, s {signals = signals ++ [toS <$> s0]})+        liftWithState init $ \(i, s) ->+            if i == idx+                then do+                    let my = fromS s+                    S.put my+                    pure my+                else S.get+    runSignals comp = do+        (comp', s) <- S.runStateT comp mempty+        chan <- BC.newBoundedChan 100+        bracket+            (do forM (zip [0 ..] $ signals s) $ \(idx, sig) ->+                    Conc.forkIO $ forever $ do+                        event <- sig+                        BC.writeChan chan $ Just (idx, event))+            (\threads -> do+                 printSignalD "Killing signal threads"+                 mapM_ Conc.killThread threads)+            (\_ -> do+                 printSignalD "signals done"+                 let signalGen =+                         ioReaderToGenerator @(Generator IO) (BC.readChan chan)+                 runOhuaM (smapGen comp' signalGen) $ states s)+    smapGen f = foldlGeneratorT lift (\acc i -> (: acc) <$> f i) []++type BuildSeqOhua a = StateT CollSt IO a++type SeqOhua a = ReaderT (MV.IOVector S) IO a++main, main0 :: IO ()+main = main0++main0 = do+    [confPath] <- getArgs+    runSequential <- maybe False (/= "") <$> lookupEnv "SEQUENTIAL"+    void $+        bracket+            (setup confPath)+            (\((_, closeKafka), _redisConn, stats) -> do+                 putStrLn "Closing resources"+                 closeKafka+                 putStrLn "Writing Statistics"+                 withFile "ohua-statistics.txt" WriteMode $ \h ->+                     writeStatistics stats h+             -- Redis.disconnect redisConn+             )+            (\((kafkaReader, _), redisConn, stats) ->+                 putStrLn "Starting execution" >>+                 if runSequential+                     then algoSeq kafkaReader redisConn stats+                     else runSignals (algo kafkaReader redisConn stats))
+ test/FakeComputation.hs view
@@ -0,0 +1,60 @@+module FakeComputation where++-- | Original source https://github.com/iu-parfunc/lvars/tree/master/archived_old/fhpc13-lvars/benchmarks+import Control.DeepSeq+import Control.Monad.State++import Control.Concurrent (myThreadId)++-- Iterates the sin function n times on its input and returns the sum+-- of all iterations.+sin_iter :: Int -> Float -> Float+sin_iter 0 x = x+sin_iter n !x = sin_iter (n - 1) (x + sin x)++cos_iter :: Int -> Float -> Float+cos_iter 0 x = x+cos_iter n !x = cos_iter (n - 1) (x + cos x)++tan_iter :: Int -> Float -> Float+tan_iter 0 x = x+tan_iter n !x = tan_iter (n - 1) (x + tan x)++wrk_sins :: Int -> Float -> Float+wrk_sins num sin_wrk =+    let res = sin_iter num (2.222 + sin_wrk)+     in force res++gwrk :: Int -> Float -> (Int -> Float -> Float) -> Float+gwrk num wrk f =+    let res = f num (2.222 + wrk)+     in force res++type ID = Int++work :: Float -> StateT (ID, Int) IO (Float, Float)+work wrk+  -- liftIO $ putStrLn $ "work: " ++ (show wrk)+ = do+    (identifier, numIter) <- get+    tId <- liftIO myThreadId+  -- liftIO $ putStrLn $ "start: " ++ (show identifier) ++ " on thread: " ++ (show tId)+  -- let r = wrk_sins numIter wrk+    let r = sin_iter numIter (2.222 + wrk)+  -- r <- liftIO $ evaluate $ force $ sin_iter numIter (2.222 + wrk) -- this is the solution!+  -- liftIO $ putStrLn $ "stop: " ++ (show identifier)+  -- liftIO $ putStrLn $ "result: " ++ (show r)+    return (wrk, r)++gwork :: (Int -> Float -> Float) -> Float -> StateT (ID, Int) IO (Float, Float)+gwork f wrk+  -- liftIO $ putStrLn $ "work: " ++ (show wrk)+ = do+    (identifier, numIter) <- get+    tId <- liftIO myThreadId+  -- liftIO $ putStrLn $ "start: " ++ (show identifier) ++ " on thread: " ++ (show tId)+    let r = gwrk numIter wrk f+  -- r <- liftIO $ evaluate $ force $ f numIter (2.222 + wrk) -- this is the solution!+  -- liftIO $ putStrLn $ "stop: " ++ (show identifier)+  -- liftIO $ putStrLn $ "result: " ++ (show r)+    return (wrk, r)
+ test/SD/Correctness.hs view
@@ -0,0 +1,251 @@+module SD.Correctness (testSuite) where++import Control.Monad.State++import Test.Framework+import Test.Framework.Providers.HUnit+import Test.HUnit hiding (State)++-- import Data.Monoid+-- import Utils+import Control.Monad.SD+import Data.StateElement++foo :: Int -> StateT Int IO Int+foo x = do+    s <- get+    put $ s + 2+    return $ x + 2++bar :: Int -> StateT Int IO Int+bar x = do+    s <- get+    put $ s + 3+    return $ x * 3++barFloat :: Int -> StateT Float IO Int+barFloat x = do+    s <- get+    put $ s + 3.34+    return $ x * 3++-- simpleAlgo :: Int -> OhuaM ([LocalStateBox Int], [LocalStateBox Int]) Int+simpleComposition v = do+    c <- return v+    r0 <- liftWithIndex 0 foo c+    r1 <- liftWithIndex 1 bar r0+    return r1++packagedSimpleComposition = do+    f0 <- liftWithState (return 0) foo+    f1 <- liftWithState (return 0) bar+    return $ f1 <=< f0++simpleCompositionHetState v = do+    c <- return v+    r0 <- liftWithIndex 0 foo c+    r1 <- liftWithIndex 1 barFloat r0+    return r1++simpleSMap = smap simpleComposition++smapWithContext v = do+    c <- return v+    r0 <- liftWithIndex 2 foo c+    r1 <- liftWithIndex 3 bar r0+    r2 <- smap simpleComposition [r0, r1]+    return r2++smapResultUsed v = do+    c <- return v+    r0 <- liftWithIndex 2 foo c+    r1 <- liftWithIndex 3 bar r0+    r2 <- smap simpleComposition [r0, r1]+    r3 <- liftWithIndex 4 foo $ r2 !! 0+    r4 <- liftWithIndex 5 bar $ r2 !! 1+    return (r3, r4)++packagedSmapResultUsed = do+    f0 <- liftWithState (return 0) foo+    f1 <- liftWithState (return 0) bar+    f2 <- smapSTC packagedSimpleComposition+    f3 <- liftWithState (return 0) foo+    f4 <- liftWithState (return 0) bar+    return $ \v -> do+        r0 <- f0 v+        r1 <- f1 r0+        r2 <- f2 [r0, r1]+        r3 <- f3 $ r2 !! 0+        r4 <- f4 $ r2 !! 1+        return (r3, r4)++smapOverEmptyList = do+    r1 <- smap simpleComposition []+    smap someComp [length r1]+  where+    someComp i = do+        r0 <- liftWithIndex 2 foo i+        liftWithIndex 3 bar r0++smapOverEmptyList2 = smap (smap simpleComposition) [[], [1 .. 3]]++simpleCompositionPackaged v = do+    c <- return v+    r0 <- liftWithIndex 0 foo c+    r1 <- liftWithIndex 1 bar r0+    return r1++caseComp idxFoo idxBranch1 idxBranch2 v = do+    c <- liftWithIndex idxFoo foo v+    o <- case_ c [(4, branch1 c), (8, branch2 c)]+    return o+  where+    branch1 = liftWithIndex idxBranch1 bar+    branch2 = liftWithIndex idxBranch2 bar++caseComposition = caseComp 0 1 2++smapWithCase = smap caseComposition++nestedCase v = do+    o <- case_ v [(2, caseComp 0 1 2 v), (6, caseComp 3 4 5 v)]+    return o++ret = return (10 :: Int)++returnTest :: Assertion+returnTest = do+    (result, s) <- runOhuaM ret []+    assertEqual "result was wrong." (10 :: Int) result+    assertEqual "state was wrong." [] (map fromS s :: [Int])++bindTest :: Assertion+bindTest = do+    (result, s) <- runOhuaM (simpleComposition 10) $ map toS [0 :: Int, 0]+    assertEqual "result was wrong." 36 result+    assertEqual "state was wrong." [2, 3] (map fromS s :: [Int])++hetStateTest :: Assertion+hetStateTest = do+    (result, s1:(s2:_)) <-+        runOhuaM+            (simpleCompositionHetState 10)+            [toS (0 :: Int), toS (2.5 :: Float)]+    assertEqual "result was wrong." 36 result+    assertEqual "state was wrong." 2 (fromS s1 :: Int)+    assertEqual "state was wrong." 5.84 (fromS s2 :: Float)+  -- assertEqual "state was wrong." 2 (toConcrete s1 :: Int)+  -- assertEqual "state was wrong." 5.84 (toConcrete s2 :: Float)++pipeSMapTest :: Assertion+pipeSMapTest = do+    (result, s) <- runOhuaM (simpleSMap [10, 10]) $ map toS [0 :: Int, 0]+    assertEqual "result was wrong." [36, 36] result+    assertEqual "state was wrong." [4, 6] (map fromS s :: [Int])++smapContextTest :: Assertion+smapContextTest = do+    (result, s) <- runOhuaM (smapWithContext 10) $ map toS [0 :: Int, 0, 0, 0]+    assertEqual "result was wrong." [42, 114] result+    assertEqual "state was wrong." [4, 6, 2, 3] (map fromS s :: [Int])++smapResultUsedTest :: Assertion+smapResultUsedTest = do+    (result, s) <-+        runOhuaM (smapResultUsed 10) $ map toS [0 :: Int, 0, 0, 0, 0, 0]+    assertEqual "result was wrong." (44, 342) result+    assertEqual "state was wrong." [4, 6, 2, 3, 2, 3] (map fromS s :: [Int])++-- | Basically the same as 'smapResultUsed' but order of elements in the state+-- is different, because of the order in which the state monad collects the+-- indices.+packagedSmapResultUsedTest :: Assertion+packagedSmapResultUsedTest = do+    (result, s) <- runSTCLang packagedSmapResultUsed 10+    assertEqual "result was wrong." (44, 342) result+    assertEqual "state was wrong." [2, 3, 4, 6, 2, 3] (map fromS s :: [Int])++packagedBindTest :: Assertion+packagedBindTest = do+    (result, s) <-+        runOhuaM (simpleCompositionPackaged 10) $ map toS [0 :: Int, 0]+    assertEqual "result was wrong." 36 result+    assertEqual "state was wrong." [2, 3] (map fromS s :: [Int])++caseTest :: Assertion+caseTest+  -- "true" branch+ = do+    (result, s) <- runOhuaM (caseComposition 2) $ map toS [0 :: Int, 0, 0]+    assertEqual "result was wrong." 12 result+    assertEqual "state was wrong." [2, 3, 0] (map fromS s :: [Int])+  -- "false" branch+    (result', s') <- runOhuaM (caseComposition 6) $ map toS [0 :: Int, 0, 0]+    assertEqual "result was wrong." 24 result'+    assertEqual "state was wrong." [2, 0, 3] (map fromS s' :: [Int])++caseSmapTest :: Assertion+caseSmapTest+  -- "true" branch+ = do+    (result, s) <- runOhuaM (smapWithCase [2, 6]) $ map toS [0 :: Int, 0, 0]+    assertEqual "result was wrong." [12, 24] result+    assertEqual "state was wrong." [4, 3, 3] (map fromS s :: [Int])+  -- execute only once+    (result, s) <- runOhuaM (smapWithCase [2]) $ map toS [0 :: Int, 0, 0]+    assertEqual "result was wrong." [12] result+  --assertEqual "state was wrong." [4,3,3] (map fromS s :: [Int])++nestedCaseTest :: Assertion+nestedCaseTest+  -- "true" branch+ = do+    (result, s) <- runOhuaM (nestedCase 2) $ map toS [0 :: Int, 0, 0, 0, 0, 0]+    assertEqual "result was wrong." 12 result+    assertEqual "state was wrong." [2, 3, 0, 0, 0, 0] (map fromS s :: [Int])+  -- "false" branch+    (result', s') <- runOhuaM (nestedCase 6) $ map toS [0 :: Int, 0, 0, 0, 0, 0]+    assertEqual "result was wrong." 24 result'+    assertEqual "state was wrong." [0, 0, 0, 2, 0, 3] (map fromS s' :: [Int])++tooMuchStateTest :: Assertion+tooMuchStateTest = do+    (result, s) <- runOhuaM ret $ map toS [0 :: Int]+    assertEqual "result was wrong." (10 :: Int) result+    assertEqual "state was wrong." [0] (map fromS s :: [Int])++notEnoughStateTest :: Assertion+notEnoughStateTest = do+    (result, s) <- runOhuaM (simpleComposition 10) $ map toS [0 :: Int]+    assertEqual "result was wrong." 36 result+    assertEqual "state was wrong." [2, 3] (map fromS s :: [Int])++smapHandlesEmptyList :: Assertion+smapHandlesEmptyList =+    void (runOhuaM smapOverEmptyList (map toS [0 .. 3 :: Int]))++smapHandlesEmptyList2 :: Assertion+smapHandlesEmptyList2 = do+    (res, _) <- runOhuaM smapOverEmptyList2 (map toS [0 .. 1 :: Int])+    assertEqual "lengths differ" [0, 3] (map length res)++testSuite :: Test.Framework.Test+testSuite =+    testGroup+        "Futures"+        [ testCase "checking monadic return" returnTest+        , testCase "checking monadic bind" bindTest+        , testCase "checking simple pipe smap" pipeSMapTest+        , testCase "checking smap with context" smapContextTest+        , testCase "checking smap result used" smapResultUsedTest+        , testCase "smap over empty list" smapHandlesEmptyList+        , testCase "nested smap over empty list" smapHandlesEmptyList2+        , testCase "checking packaged version" packagedBindTest+        , testCase "checking case statement" caseTest+        , testCase "checking smap-case composition" caseSmapTest+        , testCase "simple nested case composition" nestedCaseTest+        , testCase "heterogeneous state" hetStateTest+        , testCase "test packaged state" packagedSmapResultUsedTest+            -- , testCase "Futures: too much state" tooMuchStateTest --> this turns into an Error in monad-par that says: "no result"+            -- , testCase "Futures: not enough state" notEnoughStateTest --> turns into the error: Prelude.!!: index too large+        ]
+ test/SD/Performance.hs view
@@ -0,0 +1,56 @@+module SD.Performance (testSuite) where++import Test.Framework+import Test.Framework.Providers.HUnit+import Test.HUnit hiding (State)++import FakeComputation (work, wrk_sins)++import Control.Monad.SD+import Data.StateElement++import Data.Time.Clock.POSIX++import GHC.Conc (getNumCapabilities, numCapabilities, setNumCapabilities)++currentTimeMillis = round . (* 1000) <$> getPOSIXTime++pipeline v = do+    c <- return v+    (_, r0) <- liftWithIndex 0 work c+    (_, r1) <- liftWithIndex 1 work r0+    (_, r2) <- liftWithIndex 2 work r1+    (_, r3) <- liftWithIndex 3 work r2+    return r3++fourStepPipeline = smap pipeline++-- Beware: You need to recompile with "-threaded" in order to  enable concurrency!+--         Just changing the cabal file and running `stack test` won't work.+--         Instead always do `stack clean && stack test`+pipeSMapTest :: Assertion+pipeSMapTest = do+    let a = 3000000 :: Float+    let b = 2000000 :: Int+    let inputs = replicate 4 a+    let r = wrk_sins b a+    let expectedOutputs = replicate 4 r+    putStrLn $ "num cores (RTS option): " ++ (show numCapabilities)+    (\x -> putStrLn $ "num cores: " ++ show x) =<< getNumCapabilities+    start <- currentTimeMillis+    (result, _) <-+        runOhuaM (fourStepPipeline inputs) $+        map toS $ [(0 :: Int, b), (1, b), (2, b), (3, b)]+    stop <- currentTimeMillis+    putStrLn $ "Exec time [ms]: " ++ (show $ stop - start)+    assertEqual "result was wrong." expectedOutputs result++coresTest = mapM_ runTest [1 .. 4]+  where+    runTest numCores = do+        setNumCapabilities numCores+        pipeSMapTest+      -- TODO validation needed! (for now, check the exec times)++testSuite :: Test.Framework.Test+testSuite = testGroup "Performance FBM" [testCase "4-step pipeline" coresTest]
+ test/Spec.hs view
@@ -0,0 +1,12 @@+{-# LANGUAGE OverloadedLists #-}+import Test.Framework++import SD.Correctness as FBM+import SD.Performance as PFBM+++main :: IO ()+main =+    defaultMain+        [FBM.testSuite, PFBM.testSuite]+-- main = flip defaultMainWithOpts mempty FBM.testSuite