packages feed

essence-of-live-coding 0.2.3 → 0.2.4

raw patch · 10 files changed

+299/−27 lines, 10 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- LiveCoding.Cell.Feedback: keep :: (Data a, Monad m) => a -> Cell m (Maybe a) a
- LiveCoding.Cell.Feedback: keepJust :: (Monad m, Data a) => Cell m (Maybe a) (Maybe a)
+ LiveCoding: Pop :: BufferCommand a
+ LiveCoding: Push :: a -> BufferCommand a
+ LiveCoding: boundedFIFO :: (Data a, Monad m) => Int -> Cell m (Maybe a) (Seq a)
+ LiveCoding: buffer :: (Monad m, Data a) => Cell m [BufferCommand a] (Maybe a)
+ LiveCoding: buffered :: (Monad m, Data a) => Cell m (Maybe a) (Maybe b) -> Cell m (Maybe a) (Maybe b)
+ LiveCoding: data BufferCommand a
+ LiveCoding: edge :: Monad m => Cell m Bool Bool
+ LiveCoding: foldC' :: (Data b, Monad m) => (a -> b -> b) -> b -> Cell m a b
+ LiveCoding: maybePop :: Maybe a -> [BufferCommand b]
+ LiveCoding: maybePush :: Maybe a -> [BufferCommand a]
+ LiveCoding.Cell.Util: Pop :: BufferCommand a
+ LiveCoding.Cell.Util: Push :: a -> BufferCommand a
+ LiveCoding.Cell.Util: boundedFIFO :: (Data a, Monad m) => Int -> Cell m (Maybe a) (Seq a)
+ LiveCoding.Cell.Util: buffer :: (Monad m, Data a) => Cell m [BufferCommand a] (Maybe a)
+ LiveCoding.Cell.Util: buffered :: (Monad m, Data a) => Cell m (Maybe a) (Maybe b) -> Cell m (Maybe a) (Maybe b)
+ LiveCoding.Cell.Util: data BufferCommand a
+ LiveCoding.Cell.Util: edge :: Monad m => Cell m Bool Bool
+ LiveCoding.Cell.Util: foldC' :: (Data b, Monad m) => (a -> b -> b) -> b -> Cell m a b
+ LiveCoding.Cell.Util: keep :: (Data a, Monad m) => a -> Cell m (Maybe a) a
+ LiveCoding.Cell.Util: keepJust :: (Monad m, Data a) => Cell m (Maybe a) (Maybe a)
+ LiveCoding.Cell.Util: maybePop :: Maybe a -> [BufferCommand b]
+ LiveCoding.Cell.Util: maybePush :: Maybe a -> [BufferCommand a]

Files

CHANGELOG.md view
@@ -1,5 +1,10 @@ # Revision history for essence-of-live-coding +## 0.2.4++* Extended testing utilities+* Extended LiveCoding.Cell.Util by buffer, edge, boundedFIFO and other utilities+ ## 0.2.3  * Added printTimeC debugging utility
essence-of-live-coding.cabal view
@@ -1,5 +1,5 @@ name:                essence-of-live-coding-version:             0.2.3+version:             0.2.4 synopsis: General purpose live coding framework description:   essence-of-live-coding is a general purpose and type safe live coding framework.@@ -93,7 +93,10 @@   type: exitcode-stdio-1.0   main-is: Main.hs   other-modules:-      Feedback+      Cell+    , Cell.Monad.Trans+    , Cell.Util+    , Feedback     , Handle     , Handle.LiveProgram     , Monad
src/LiveCoding/Cell/Feedback.lhs view
@@ -77,25 +77,3 @@ sumFeedback = feedback 0 $ arr   $ \(a, accum) -> (accum, a + accum) \end{code}--\fxerror{Mention keepJust and keep}-\begin{comment}-\begin{code}-keepJust-  :: (Monad m, Data a)-  => Cell m (Maybe a) (Maybe a)-keepJust = feedback Nothing $ arr keep-  where-    keep (Nothing, Nothing) = (Nothing, Nothing)-    keep (_, Just a) = (Just a, Just a)-    keep (Just a, Nothing) = (Just a, Just a)---- | Initialise with a value 'a'.---   If the input is 'Nothing', @keep a@ will output the stored indefinitely.---   A new value can be stored by inputting 'Maybe a'.-keep :: (Data a, Monad m) => a -> Cell m (Maybe a) a-keep a = feedback a $ proc (ma, aOld) -> do-  let aNew = fromMaybe aOld ma-  returnA -< (aNew, aNew)-\end{code}-\end{comment}
src/LiveCoding/Cell/Util.hs view
@@ -6,7 +6,13 @@ import Control.Arrow import Control.Monad.IO.Class import Data.Data (Data)+import Data.Functor (void)+import Data.Maybe +-- containers+import Data.Sequence hiding (take)+import qualified Data.Sequence as Sequence+ -- time import Data.Time.Clock @@ -14,6 +20,8 @@ import LiveCoding.Cell import LiveCoding.Cell.Feedback +-- * State accumulation+ -- | Sum all past inputs, starting by the given number sumFrom :: Monad m => Integer -> Cell m Integer Integer sumFrom n0 = feedback n0 $ proc (n, acc) -> returnA -< (acc, acc + n)@@ -31,6 +39,43 @@   where     cellStep b a = let b' = step a b in return (b, b') +-- | Like 'foldC', but does not delay the output.+foldC' :: (Data b, Monad m) => (a -> b -> b) -> b -> Cell m a b+foldC' step cellState = Cell { .. }+  where+    cellStep b a = let b' = step a b in return (b', b')++-- | Initialise with a value 'a'.+--   If the input is 'Nothing', @keep a@ will output the stored indefinitely.+--   A new value can be stored by inputting 'Maybe a'.+keep :: (Data a, Monad m) => a -> Cell m (Maybe a) a+keep a = feedback a $ proc (ma, aOld) -> do+  let aNew = fromMaybe aOld ma+  returnA -< (aNew, aNew)++-- | Like 'keep', but returns 'Nothing' until it is initialised by a @'Just' a@ value.+keepJust+  :: (Monad m, Data a)+  => Cell m (Maybe a) (Maybe a)+keepJust = feedback Nothing $ arr keep+  where+    keep (Nothing, Nothing) = (Nothing, Nothing)+    keep (_, Just a) = (Just a, Just a)+    keep (Just a, Nothing) = (Just a, Just a)++-- | @boundedFIFO n@ keeps the first @n@ present values.+boundedFIFO :: (Data a, Monad m) => Int -> Cell m (Maybe a) (Seq a)+boundedFIFO n = foldC' step empty+  where+    step Nothing  as = as+    step (Just a) as = Sequence.take n $ a <| as++-- | Returns 'True' iff the current input value is 'True' and the last input value was 'False'.+edge :: Monad m => Cell m Bool Bool+edge = proc b -> do+  bLast <- delay False -< b+  returnA -< b && not bLast+ -- * Debugging utilities  -- | Print the current UTC time, prepended with the first 8 characters of the given message.@@ -40,3 +85,60 @@ -- | Like 'printTime', but as a cell. printTimeC :: MonadIO m => String -> Cell m () () printTimeC msg = constM $ printTime msg++-- * Buffers++-- | A command to send to 'buffer'.+data BufferCommand a+  = Push a+  -- ^ Add an 'a' to the buffer.+  | Pop+  -- ^ Remove the oldest element from the buffer.++-- | Pushes @'Just' a@ and does nothing on 'Nothing'.+maybePush :: Maybe a -> [BufferCommand a]+maybePush = (Push <$>) . maybeToList++-- | Pops on @'Just' a@ and does nothing on 'Nothing'.+maybePop :: Maybe a -> [BufferCommand b]+maybePop = (const Pop <$>) . maybeToList++{- | Single-consumer, multi-producer buffer.++The output value is the oldest value in the buffer,+if it exists.++* Add elements by inputting @'Push' a@.+* Remove elements by inputting 'Pop'.+-}+buffer :: (Monad m, Data a) => Cell m [BufferCommand a] (Maybe a)+buffer = Cell { .. }+  where+    cellState = empty+    cellStep as commands = return (currentHead as, nextBuffer as commands)+    currentHead as = case viewl as of+      EmptyL   -> Nothing+      a :< as' -> Just a+    nextBuffer as [] = as+    nextBuffer as (Push a : commands) = nextBuffer (as |> a) commands+    nextBuffer as (Pop : commands) = nextBuffer (Sequence.drop 1 as) commands++{- | Equip a 'Cell' with a 'buffer'.++* Whenever @'Just' a@ value enters @buffered cell@,+  it is added to the buffer.+* Whenever @cell@ emits @'Just' b@,+  the oldest value is dropped from the buffer.+* @cell@ is always fed with 'Just' the oldest value from the buffer,+  except when the buffer is empty, then it is fed 'Nothing'.++This construction guarantees that @cell@ produces exactly one output for every input value.+-}+buffered+  :: (Monad m, Data a)+  => Cell m (Maybe a) (Maybe b)+  -> Cell m (Maybe a) (Maybe b)+buffered cell = feedback Nothing $ proc (aMaybe, ticked) -> do+  aMaybe' <- buffer -< maybePop ticked ++ maybePush aMaybe+  bMaybe' <- cell   -< aMaybe'+  returnA           -< (bMaybe', void bMaybe')
+ test/Cell.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE ScopedTypeVariables #-}+module Cell where++-- base+import Prelude hiding (id)+import Control.Category+import Data.Functor.Identity++-- transformers+import Control.Monad.Trans.Identity++-- test-framework+import Test.Framework++-- test-framework-quickcheck2+import Test.Framework.Providers.QuickCheck2++-- QuickCheck+import Test.QuickCheck++-- essence-of-live-coding+import LiveCoding++import qualified Cell.Util+import qualified Cell.Monad.Trans++test = testGroup "Cell"+  [ testProperty "steps produces outputs"+    $ \(inputs :: [Int]) -> inputs === fst (runIdentity $ steps (id :: Cell Identity Int Int) inputs)+  , Cell.Util.test+  , Cell.Monad.Trans.test+  ]
+ test/Cell/Monad/Trans.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE Arrows #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Cell.Monad.Trans where++-- transformers+import Control.Monad.Trans.Reader++-- test-framework+import Test.Framework++-- test-framework-quickcheck2+import Test.Framework.Providers.QuickCheck2++-- QuickCheck+import Test.QuickCheck hiding (output)++-- essence-of-live-coding+import LiveCoding++import Util++test = testGroup "Cell.Monad.Trans"+  [ testProperty "readerC" $ inIdentityT $ proc (n :: Int) -> do+      nReader <- runReaderC' $ constM ask -< (n, ())+      returnA -< n === nReader+  ]
+ test/Cell/Util.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE Arrows #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Cell.Util where++-- base+import qualified Control.Category as C+import Data.Functor.Identity+import Data.Maybe+import Control.Monad++-- transformers+import Control.Monad.Trans.Reader++-- test-framework+import Test.Framework++-- test-framework-quickcheck2+import Test.Framework.Providers.QuickCheck2++-- QuickCheck+import Test.QuickCheck hiding (output)++-- essence-of-live-coding+import LiveCoding++import Util++test = testGroup "Utility unit tests"+--   [ testProperty "Buffer works as expected" CellSimulation+--       { cell = buffer+--       , input = []+--     --   , input =+--     --       [ []+--     --       , [Pop]+--     --       , [Push (23 :: Int)]+--     --       , []+--     --       , [Pop, Pop]+--     --       , [Push 42, Pop]+--     --       , [Push 1, Push 2]+--     --       , []+--     --       , []+--     --       , [Pop, Push 3]+--     --       , []+--     --       , [Pop]+--     --       , [Pop]+--     --       , []+--     --       ]+--       , output = []+--     --   , output =+--     --       [ Nothing+--     --       , Nothing+--     --       , Just 23+--     --       , Just 23+--     --       , Nothing+--     --       , Nothing+--     --       , Just 1+--     --       , Just 1+--     --       , Just 1+--     --       , Just 2+--     --       , Just 2+--     --       , Just 3+--     --       , Nothing+--     --       , Nothing+--     --       ]+--       }+  [ testProperty "buffered works as expected" CellSimulation+    { cell = buffered C.id+    , input =+        [ Just (23 :: Int)+        ]+    , output =+        [ Nothing+        ]+    }+  , testProperty "buffered can be used in an asynchronous setting" $ do+      jointInputs <- arbitrary -- Simulates when input arrives and when inner cell is activated+      -- Make sure cell is ticked at the last time, and no new input arrives+      let innerCell = proc (aMaybe :: Maybe Int) -> do+            isScheduled <- constM ask -< ()+            returnA -< guard isScheduled >> aMaybe+          outerCell = buffered innerCell+          (outputs, _) = runIdentity $ steps (runReaderC' outerCell) jointInputs+          labelString = unwords [show jointInputs, show outputs, show $ length jointInputs, show $ length outputs]+          inputs = snd $ unzip jointInputs+          bufferNotEmpty = isJust $ listToMaybe $ reverse outputs+      -- Make sure each message arrived exactly once, in order+      return+        $ counterexample labelString+        $ catMaybes inputs === catMaybes outputs+        .||. bufferNotEmpty+  ]
test/Main.hs view
@@ -17,14 +17,17 @@ import Test.QuickCheck  -- essence-of-live-coding+import qualified Cell import qualified Feedback import qualified Handle import qualified Monad+import qualified Monad.Trans+ import LiveCoding -import qualified Monad.Trans import qualified TestData.Foo1 as Foo1 import qualified TestData.Foo2 as Foo2+ import Util  intToInteger :: Int -> Integer@@ -107,6 +110,7 @@         , output2 = [23, 24, 25]         }       ]+    , Cell.test     , Handle.test     , Monad.test     , Feedback.test
test/Monad/Trans.hs view
@@ -20,6 +20,6 @@     $ \(stateT :: Int) (stateInternal :: Int)       -> State { .. } === migrate State { stateInternal = 23, .. } stateInternal   , testProperty "Migrates from runStateL"-  $ \(stateT :: Int) (stateInternal :: Int)-    -> stateInternal === migrate 42 State { .. }+    $ \(stateT :: Int) (stateInternal :: Int)+      -> stateInternal === migrate 42 State { .. }   ]
test/Util.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE RecordWildCards #-} module Util where @@ -50,3 +51,33 @@   let cell2' = hotCodeSwapCell cell2 cell1'   (bs2, _) <- steps cell2' as2   return (bs1, bs2)++-- FIXME move to essence-of-live-coding-quickcheck+-- https://github.com/turion/essence-of-live-coding/issues/36+instance (Arbitrary a, Testable prop) => Testable (Cell Identity a prop) where+  property cell = property $ do+    as <- arbitrary+    let (props, _) = runIdentity $ steps cell as+    return $ conjoin props++-- | Helper to unify cells to the 'Identity' monad.+inIdentityT :: Cell Identity a prop -> Cell Identity a prop+inIdentityT = id++-- | Basic unit test for 'Cell's.+--   Check whether a given 'input' to your 'cell' results in a given 'output'.+data CellSimulation a b = CellSimulation+  { cell :: Cell Identity a b+  , input :: [a]+  , output :: [b]+  }++instance (Eq b, Show b) => Testable (CellSimulation a b) where+  property CellSimulation { .. } = property CellMigrationSimulation+    { cell1 = cell+    , cell2 = cell+    , input1 = input+    , input2 = []+    , output1 = output+    , output2 = []+    }