packages feed

machines 0.4.1 → 0.5.1

raw patch · 14 files changed

+272/−23 lines, 14 filesdep +conduit-combinatorsdep −hastachedep −statisticsdep ~conduitdep ~criteriondep ~mtl

Dependencies added: conduit-combinators

Dependencies removed: hastache, statistics

Dependency ranges changed: conduit, criterion, mtl, pipes, profunctors

Files

CHANGELOG.markdown view
@@ -1,3 +1,16 @@+0.5.1+-----+* `profunctors` 5 support+* GHC 7.10 warnings have been cleaned up++0.5+---+* Major bug fix (and semantic change) for `Plan`'s `(<|>)`.++0.4.2+-----+* Add `Monoid` and `Semigroups` instances for `MachineT`+ 0.4.1 ----- * Support `void` 0.7, fixed upper bounds on dependencies going forward.
benchmarks/Benchmarks.hs view
@@ -4,6 +4,7 @@ import Control.Monad.Identity import Criterion.Main import qualified Data.Conduit      as C+import qualified Data.Conduit.Combinators as CC import qualified Data.Conduit.List as C import qualified Data.Machine      as M import qualified Pipes             as P@@ -44,11 +45,12 @@   , bgroup "dropWhile"       [ bench "machines" $ whnf drainM (M.droppingWhile (<= value))       , bench "pipes" $ whnf drainP (P.dropWhile (<= value))+      , bench "conduit" $ whnf drainC (CC.dropWhile (<= value))       ]   , bgroup "scan"       [ bench "machines" $ whnf drainM (M.scan (+) 0)       , bench "pipes" $ whnf drainP (P.scan (+) 0 id)-      , bench "conduit" $ whnf drainC (C.scanl (\a s -> let b = a+s in (b,b)) 0)+      , bench "conduit" $ whnf drainC (CC.scanl (+) 0)       ]   , bgroup "take"       [ bench "machines" $ whnf drainM (M.taking value)@@ -58,9 +60,11 @@   , bgroup "takeWhile"       [ bench "machines" $ whnf drainM (M.takingWhile (<= value))       , bench "pipes" $ whnf drainP (P.takeWhile (<= value))+      , bench "conduit" $ whnf drainSC (CC.takeWhile (<= value) C.=$= C.sinkNull)       ]   , bgroup "fold"       [ bench "machines" $ whnf drainM (M.fold (+) 0)       , bench "pipes" $ whnf (P.fold (+) 0 id) sourceP+      , bench "conduit" $ whnf drainSC (C.fold (+) 0)       ]   ]
examples/Examples.hs view
@@ -6,6 +6,7 @@ import Control.Exception import Control.Monad.Trans import Data.Machine+import Data.Machine.Group import System.IO  -- this slurp slurps until an eof exception is raised.@@ -89,6 +90,17 @@   t = tee (source [1..]) (getFileLines path echo) lineNumsT   lineNumsT :: MachineT IO (T Integer String) String   lineNumsT = repeatedly $ zipWithT $ \i s -> show i ++ ": " ++ s++uniq :: Bool+uniq = run (supply xs uniqMachine) == [1,2,3] where+  -- | Unix's "uniq" command using groupingOn+  -- (==)  means "groups are contiguous values"+  -- final means "run the 'final' machine over each group"+  uniqMachine :: (Monad m, Eq a) => ProcessT m a a+  uniqMachine = groupingOn (==) final ++  xs :: [Int]+  xs = [1,2,2,3,3,3]  {- def lineWordCount(fileName: String) =
machines.cabal view
@@ -1,6 +1,6 @@ name:          machines category:      Control, Enumerator-version:       0.4.1+version:       0.5.1 license:       BSD3 cabal-version: >= 1.10 license-file:  LICENSE@@ -41,7 +41,7 @@     containers   >= 0.3   && < 0.6,     free         >= 3.1.1 && < 5,     pointed      >= 3     && < 5,-    profunctors  >= 3     && < 5,+    profunctors  >= 3     && < 6,     semigroups   >= 0.8.3 && < 1,     transformers >= 0.3   && < 0.5,     mtl          >= 2     && < 2.3,@@ -60,6 +60,8 @@     Data.Machine.Tee     Data.Machine.Type     Data.Machine.Wye+    Data.Machine.Group+    Data.Machine.Pipe    default-language: Haskell2010   other-extensions:@@ -81,7 +83,7 @@     base == 4.*,     directory >= 1.0 && < 1.3,     doctest >= 0.8 && <= 0.10,-    filepath >= 1.3 && < 1.4+    filepath >= 1.3 && < 1.5   ghc-options: -Wall -Werror -threaded   hs-source-dirs: tests @@ -93,11 +95,10 @@   ghc-options:      -O2 -rtsopts -threaded    build-depends:-    base == 4.*,-    conduit == 1.0.17.1,-    criterion == 0.6.2.*,-    hastache < 0.6,+    base                == 4.*,+    conduit             >= 1.0   && < 1.3,+    conduit-combinators >= 0.2.5 && < 0.4,+    criterion           >= 0.6   && < 1.1,     machines,-    mtl == 2.1.2,-    pipes == 4.1.0,-    statistics < 0.11+    mtl                 >= 2     && < 2.3,+    pipes               >= 4     && < 4.2
src/Data/Machine/Fanout.hs view
@@ -10,6 +10,7 @@ import Data.Monoid import Data.Semigroup (Semigroup(sconcat)) import Data.List.NonEmpty (NonEmpty((:|)))+import Prelude  -- | Feed a value to a 'ProcessT' at an 'Await' 'Step'. If the -- 'ProcessT' is awaiting a value, then its next step is
+ src/Data/Machine/Group.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE GADTs #-}+module Data.Machine.Group+  ( groupingOn+  , taggedBy+  , partitioning+  , starve+  , awaitUntil+  )where+import Data.Machine++isLeft :: Either a b -> Bool+isLeft = either (const True) (const False)++-- | Using a function to signal group changes, apply a machine independently over each group.+groupingOn :: Monad m => (a -> a -> Bool) -> ProcessT m a b -> ProcessT m a b+groupingOn f m = taggedBy f ~> partitioning m++-- | Mark a transition point between two groups as a function of adjacent elements.+-- @+-- 'runT' ('supply' [1,2,2] ('taggedBy' (==))) == [Right 1, Left (), Right 2, Right 2]+-- @+taggedBy :: Monad m => (a -> a -> Bool) -> ProcessT m a (Either () a)+taggedBy f = construct $ await >>= go+  where go x = do+          yield (Right x)+          y <- await+          if not (f x y) then (yield (Left ()) >> go y) else go y+++-- | Run a machine multiple times over partitions of the input stream specified by +-- Left () values.+partitioning :: Monad m => ProcessT m a b -> ProcessT m (Either () a) b+partitioning s = go s where+  go m = MachineT $ runMachineT m >>= \v -> case v of+    -- Machine stops (possibly before inputs)+    Stop            -> runMachineT $ awaitUntil isLeft (const $ go s)++    -- Machine yields a value+    Yield o r       -> return $ Yield o (go r)++    -- Machine waits for a value+    Await f Refl r  -> return $ Await g Refl (starve r $ encased Stop)+      where+        -- No change: unwrap input and give to underlying machine.+        g (Right a) = go (f a)+        -- New group: starve r, then wait for more input (restarting machine)+        -- NOTE: if Left () happens with no more input, this will be wrong-ish(?)+        -- Meaning of "Left ()" is "stop old machine and immediately start new one."+        -- That means input [Right 1, Left ()] is different to [Right 1]+        g (Left  ()) = starve r $ go s++-- | Run a machine with no input until it stops, then behave as another machine..+starve :: Monad m => ProcessT m a b -> MachineT m k b -> MachineT m k b+starve m cont = MachineT $ runMachineT m >>= \v -> case v of+  Stop            -> runMachineT cont -- Continue with cont instead of stopping+  Yield o r       -> return $ Yield o (starve r cont)+  Await _ Refl r  -> runMachineT (starve r cont)++-- | Read inputs until a condition is met, then behave as cont with+-- | input matching condition as first input of cont.+-- | If await fails, stop.+awaitUntil :: Monad m => (a -> Bool) -> (a -> ProcessT m a b) -> ProcessT m a b+awaitUntil f cont = encased $ Await g Refl (encased Stop)+  where g a = if f a then cont a else awaitUntil f cont
src/Data/Machine/Is.hs view
@@ -16,6 +16,7 @@  import Control.Category import Data.Monoid+import Prelude  -- | Witnessed type equality data Is a b where
src/Data/Machine/Moore.hs view
@@ -30,6 +30,7 @@ import Data.Monoid import Data.Pointed import Data.Profunctor+import Prelude  -- | 'Moore' machines data Moore a b = Moore b (a -> Moore a b)
+ src/Data/Machine/Pipe.hs view
@@ -0,0 +1,136 @@+{-# LANGUAGE GADTs      #-}+{-# LANGUAGE Rank2Types #-}+-----------------------------------------------------------------------------+-- |+-- Module      :  Data.Machine.Pipe+-- Copyright   :  (C) 2015 Yorick Laupa, Gabriel Gonzalez+-- License     :  BSD-style (see the file LICENSE)+--+-- Maintainer  :  Yorick Laupa <yo.eight@gmail.com>+-- Stability   :  provisional+-- Portability :  Rank-2 Types, GADTs+--+-- Allows bidirectional communication between two MachineT. Exposed the+-- same interface of Pipes library.+----------------------------------------------------------------------------+module Data.Machine.Pipe where++import Control.Monad++import Data.Void++import Data.Machine.Plan+import Data.Machine.Type++infixl 8 >~>+infixl 7 >+>+infixl 7 >>~+infixr 6 +>>++data Exchange a' a b' b c where+  Request :: a' -> Exchange a' a b' b a+  Respond :: b  -> Exchange a' a b' b b'++type Proxy a' a b' b m c = MachineT m (Exchange a' a b' b) c++-- | 'Effect's neither 'request' nor 'respond'+type Effect m r = Proxy Void () () Void m r++-- | @Client a' a@ sends requests of type @a'@ and receives responses of+--   type @a@. 'Client's only 'request' and never 'respond'.+type Client a' a m r = Proxy a' a () Void m r++-- | @Server b' b@ receives requests of type @b'@ and sends responses of type+--   @b@. 'Server's only 'respond' and never 'request'.+type Server b' b m r = Proxy Void () b' b m r++-- | Like 'Effect', but with a polymorphic type+type Effect' m r = forall x' x y' y . Proxy x' x y' y m r++-- | Like 'Server', but with a polymorphic type+type Server' b' b m r = forall x' x . Proxy x' x b' b m r++-- | Like 'Client', but with a polymorphic type+type Client' a' a m r = forall y' y . Proxy a' a y' y m r++-- | Send a value of type a' upstream and block waiting for a reply of type a.+--  'request' is the identity of the request category.+request :: a' -> PlanT (Exchange a' a y' y) o m a+request a = awaits (Request a)++-- | Send a value of type a downstream and block waiting for a reply of type a'+--  'respond' is the identity of the respond category.+respond :: a -> PlanT (Exchange x' x a' a) o m a'+respond a = awaits (Respond a)++-- | Forward responses followed by requests.+--   'push' is the identity of the push category.+push :: Monad m => a -> Proxy a' a a' a m r+push = construct . go+  where+    go = respond >=> request >=> go++-- | Compose two proxies blocked while 'request'ing data, creating a new proxy+--   blocked while 'request'ing data.+--   ('>~>') is the composition operator of the push category.+(>~>) :: Monad m+      => (_a -> Proxy a' a b' b m r)+      -> (b -> Proxy b' b c' c m r)+      -> _a -> Proxy a' a c' c m r+(fa >~> fb) a = fa a >>~ fb++-- | (p >>~ f) pairs each 'respond' in p with an 'request' in f.+(>>~) :: Monad m+      => Proxy a' a b' b m r+      -> (b -> Proxy b' b c' c m r)+      -> Proxy a' a c' c m r+pm >>~ fb = MachineT $ runMachineT pm >>= \p ->+  case p of+    Stop                    -> return Stop+    Yield r n               -> return $ Yield r (n >>~ fb)+    Await k (Request a') ff -> return $ Await (\a -> k a >>~ fb) (Request a') (ff >>~ fb)+    Await k (Respond b) _   -> runMachineT (k +>> fb b)++-- | Forward requests followed by responses.+--   'pull' is the identity of the pull category.+pull :: Monad m => a' -> Proxy a' a a' a m r+pull = construct . go+  where+    go = request >=> respond >=> go++-- | Compose two proxies blocked in the middle of 'respond'ing, creating a new+--   proxy blocked in the middle of 'respond'ing.+--   ('>+>') is the composition operator of the pull category.+(>+>) :: Monad m+      => (b' -> Proxy a' a b' b m r)+      -> (_c' -> Proxy b' b c' c m r)+      -> _c' -> Proxy a' a c' c m r+(fb' >+> fc') c' = fb' +>> fc' c'++-- | (f +>> p) pairs each 'request' in p with a 'respond' in f.+(+>>) :: Monad m+      => (b' -> Proxy a' a b' b m r)+      -> Proxy b' b c' c m r+      -> Proxy a' a c' c m r+fb' +>> pm = MachineT $ runMachineT pm >>= \p ->+  case p of+    Stop                   -> return Stop+    Yield r n              -> return $ Yield r (fb' +>> n)+    Await k (Request b') _ -> runMachineT (fb' b' >>~ k)+    Await k (Respond c) ff -> return $ Await (\c' -> fb' +>> k c') (Respond c) (fb' +>> ff)++-- | Run a self-contained 'Effect', converting it back to the base monad.+runEffect :: Monad m => Effect m o -> m [o]+runEffect (MachineT m) = m >>= \v ->+  case v of+    Stop      -> return []+    Yield o n -> liftM (o:) (runEffect n)+    _         -> error "Data.Machine.Pipe.runEffect: impossible situation"++-- | Like 'runEffect' but discarding any produced value.+runEffect_ :: Monad m => Effect m o -> m ()+runEffect_ (MachineT m) = m >>= \v ->+  case v of+    Stop      -> return ()+    Yield _ n -> runEffect_ n+    _         -> error "Data.Machine.Pipe.runEffect_: impossible situation"
src/Data/Machine/Plan.hs view
@@ -62,7 +62,7 @@ -- | A @'Plan' k o a@ is a specification for a pure 'Machine', that reads inputs selected by @k@ -- with types based on @i@, writes values of type @o@, and has intermediate results of type @a@. ----- A @'PlanT' k o a@ can be used as a @'PlanT' k o m a@ for any @'Monad' m@.+-- A @'Plan' k o a@ can be used as a @'PlanT' k o m a@ for any @'Monad' m@. -- -- It is perhaps easier to think of 'Plan' in its un-cps'ed form, which would -- look like:@@ -103,7 +103,7 @@ instance Alternative (PlanT k o m) where   empty = PlanT $ \_ _ _ kf -> kf   {-# INLINE empty #-}-  PlanT m <|> PlanT n = PlanT $ \kp ke kr kf -> m kp ke (\ks kir _ -> kr ks kir (n kp ke kr kf)) (n kp ke kr kf)+  PlanT m <|> PlanT n = PlanT $ \kp ke kr kf -> m kp ke kr (n kp ke kr kf)   {-# INLINE (<|>) #-}  instance Monad (PlanT k o m) where
src/Data/Machine/Process.hs view
@@ -50,16 +50,16 @@   ) where  import Control.Applicative-import Control.Category+import Control.Category (Category) import Control.Monad (liftM, when, replicateM_) import Control.Monad.Trans.Class import Data.Foldable hiding (fold) import Data.Machine.Is import Data.Machine.Plan import Data.Machine.Type-import Data.Monoid (Monoid(..))+import Data.Monoid import Data.Void-import Prelude hiding ((.), id, mapM_)+import Prelude  infixr 9 <~ infixl 9 ~>@@ -77,7 +77,7 @@ -- 'Monad' @m@. type ProcessT m a b = MachineT m (Is a) b --- | An 'Automaton' is can be automatically lifted into a 'Process'+-- | An 'Automaton' can be automatically lifted into a 'Process' class Automaton k where   auto :: k a b -> Process a b @@ -225,7 +225,7 @@ -- | Break each input into pieces that are fed downstream -- individually. asParts :: Foldable f => Process (f a) a-asParts = repeatedly $ await >>= mapM_ yield+asParts = repeatedly $ await >>= traverse_ yield  -- | @sinkPart_ toParts sink@ creates a process that uses the -- @toParts@ function to break input into a tuple of @(passAlong,
src/Data/Machine/Stack.hs view
@@ -48,17 +48,15 @@ -- of pushing inputs back. stack :: Monad m => MachineT m k a -> MachineT m (Stack a) o -> MachineT m k o stack up down =-  advance1 down $ \stepD     ->+  stepMachine down $ \stepD     ->   case stepD of     Stop                     -> stopped     Yield o down'            -> encased (Yield o (up `stack` down'))     Await down' (Push a) _   -> encased (Yield a up) `stack` down' ()     Await down' Pop ffD      ->-      advance1 up $ \stepU   ->+      stepMachine up $ \stepU   ->       case stepU of         Stop                 -> stopped `stack` ffD         Yield o up'          -> up'     `stack` down' o         Await up' req ffU    -> encased (Await (\a -> up' a `stack` encased stepD) req                                                (      ffU   `stack` encased stepD))-  where-  advance1 m f = MachineT (runMachineT m >>= runMachineT . f)
src/Data/Machine/Type.hs view
@@ -42,6 +42,8 @@    , stopped +  , stepMachine+   -- * Applicative Machines   , Appliance(..)   ) where@@ -52,8 +54,9 @@ import Data.Foldable import Data.Functor.Identity import Data.Machine.Plan-import Data.Monoid+import Data.Monoid hiding ((<>)) import Data.Pointed+import Data.Semigroup import Prelude hiding ((.),id)  -------------------------------------------------------------------------------@@ -90,6 +93,10 @@ encased :: Monad m => Step k o (MachineT m k o) -> MachineT m k o encased = MachineT . return +-- | Transform a 'Machine' by looking at a single step of that machine.+stepMachine :: Monad m => MachineT m k o -> (Step k o (MachineT m k o) -> MachineT m k' o') -> MachineT m k' o'+stepMachine m f = MachineT (runMachineT . f =<< runMachineT m)+ instance Monad m => Functor (MachineT m k) where   fmap f (MachineT m) = MachineT (liftM f' m) where     f' (Yield o xs)    = Yield (f o) (f <$> xs)@@ -98,6 +105,16 @@  instance Monad m => Pointed (MachineT m k) where   point = repeatedly . yield++instance Monad m => Semigroup (MachineT m k o) where+  a <> b = stepMachine a $ \step -> case step of+    Yield o a'    -> encased (Yield o (mappend a' b))+    Await k kir e -> encased (Await (\x -> k x <> b) kir (e <> b))+    Stop          -> b++instance Monad m => Monoid (MachineT m k o) where+  mempty        = stopped+  mappend       = (<>)  -- | An input type that supports merging requests from multiple machines. class Appliance k where
tests/doctests.hs view
@@ -7,6 +7,7 @@ import System.Directory import System.FilePath import Test.DocTest+import Prelude  main :: IO () main = getSources >>= \sources -> doctest $