packages feed

automaton (empty) → 1.3

raw patch · 25 files changed

+2514/−0 lines, 25 filesdep +MonadRandomdep +QuickCheckdep +automaton

Dependencies added: MonadRandom, QuickCheck, automaton, base, mmorph, mtl, profunctors, selective, semialign, simple-affine-space, tasty, tasty-hunit, tasty-quickcheck, these, transformers

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for automaton++## 0.1.0.0++* Initial version ;)
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2024 Manuel Bärenz++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,70 @@+# `automaton`: Effectful streams and automata in initial encoding++This library defines effectful streams and automata, in initial encoding.+They are useful to define effectful automata, or state machines, transducers, monadic stream functions and similar streaming abstractions.+In comparison to most other libraries, they are implemented here with explicit state types,+and thus are amenable to GHC optimizations, often resulting in dramatically better performance.++## What?++The core concept is an effectful stream in initial encoding:+```haskell+data StreamT m a = forall s.+  StreamT+  { state :: s+  , step :: s -> m (s, a)+  }+```+This is an stream because you can repeatedly call `step` on the `state` and produce output values `a`,+while mutating the internal state.+It is effectful because each step performs a side effect in `m`, typically a monad.++The definitions you will most often find in the wild is the "final encoding":+```haskell+data StreamT m a = StreamT (m (StreamT m a, a))+```+Semantically, there is no big difference between them, and in nearly all cases you can map the initial encoding onto the final one and vice versa.+(For the single edge case, see [the section in `Data.Automaton` about recursive definitions](hackage.haskell.org/package/automaton/docs/Data.Automaton.html).)+But when composing streams,+the initial encoding will often be more performant that than the final encoding because GHC can optimise the joint state and step functions of the streams.++### How are these automata?++Effectful streams are very versatile, because you can change the effect type `m` to get a number of different concepts.+When `m` contains a `Reader` effect, you get automata!+From the effectful stream alone, a side effect, a state transition and an output value is produced at every step.+If this effect includes reading an input value, you have all ingredients for an automaton (also known as a Mealy state machine, or a transducer).++Automata can be composed in many useful ways, and are very expressive.+A lot of reactive programs can be written with them,+by composing a big program out of many automaton components.++## Why?++Mostly, performance.+When composing a big automaton out of small ones, the final encoding is not very performant, as mentioned above:+Each step of each component contains a closure, which is basically opaque for the compiler.+In the initial encoding, the step functions of two composed automata are themselves composed, and the compiler can optimize them just like any regular function.+This often results in massive speedups.++### But really, why?++To serve as the basic building block in [`rhine`](https://hackage.haskell.org/package/rhine),+a library for Functional Reactive Programming.++## Doesn't this exist already?++Not quite.+There are many streaming libraries ([`streamt`](https://hackage.haskell.org/package/streamt), [`streaming`](https://hackage.haskell.org/package/streaming)),+and state machine libraries ([`machines`](https://hackage.haskell.org/package/machines)) that implement effectful streams.+Prominently, [`dunai`](https://hackage.haskell.org/package/dunai) implements monadic stream functions+(which are essentially effectful state machines)+and has inspired the design and API of this package to a great extent.+(Feel free to extend this list by other notable libraries.)+But all of these are implemented in the final encoding.++I am aware of only two fleshed-out implementations of effectful automata in the initial encoding,+both of which have been a big inspiration for this package:++* [`essence-of-live-coding`](https://hackage.haskell.org/package/essence-of-live-coding) restricts the state type to be serializable, gaining live coding capabilities, but sacrificing on expressivity.+* https://github.com/lexi-lambda/incremental/blob/master/src/Incremental/Fast.hs is unfortunately not published on Hackage, and doesn't seem maintained.
+ automaton.cabal view
@@ -0,0 +1,108 @@+cabal-version: 3.0+name: automaton+version: 1.3+synopsis: Effectful streams and automata in initial encoding+description:+  Effectful streams have an internal state and a step function.+  Varying the effect type, this gives many different useful concepts:+  For example with a reader effect, it results in automata/transducers/state machines.++license: MIT+license-file: LICENSE+author: Manuel Bärenz+maintainer: programming@manuelbaerenz.de+category: Streaming+build-type: Simple+extra-doc-files:+  CHANGELOG.md+  README.md++source-repository head+  type: git+  location: https://github.com/turion/rhine.git++source-repository this+  type: git+  location: https://github.com/turion/rhine.git+  tag: v1.3++common opts+  build-depends:+    MonadRandom >=0.5,+    base >=4.14 && <4.20,+    mmorph ^>=1.2,+    mtl >=2.2 && <2.4,+    profunctors ^>=5.6,+    selective ^>=0.7,+    semialign >=1.2 && <=1.4,+    simple-affine-space ^>=0.2,+    these >=1.1 && <=1.3,+    transformers >=0.5,++  if flag(dev)+    ghc-options: -Werror+  ghc-options:+    -W++  default-extensions:+    Arrows+    DataKinds+    FlexibleContexts+    FlexibleInstances+    ImportQualifiedPost+    MultiParamTypeClasses+    NamedFieldPuns+    NoStarIsType+    TupleSections+    TypeApplications+    TypeFamilies+    TypeOperators++  default-language: Haskell2010++library+  import: opts+  exposed-modules:+    Data.Automaton+    Data.Automaton.Final+    Data.Automaton.Trans.Except+    Data.Automaton.Trans.Maybe+    Data.Automaton.Trans.RWS+    Data.Automaton.Trans.Random+    Data.Automaton.Trans.Reader+    Data.Automaton.Trans.State+    Data.Automaton.Trans.Writer+    Data.Stream+    Data.Stream.Except+    Data.Stream.Final+    Data.Stream.Internal+    Data.Stream.Optimized+    Data.Stream.Result++  other-modules:+    Data.Automaton.Trans.Except.Internal+    Data.Stream.Final.Except++  hs-source-dirs: src++test-suite automaton-test+  import: opts+  type: exitcode-stdio-1.0+  hs-source-dirs: test+  main-is: Main.hs+  other-modules:+    Automaton+    Automaton.Except+    Stream++  build-depends:+    QuickCheck ^>=2.14,+    automaton,+    tasty >=1.4 && <1.6,+    tasty-hunit ^>=0.10,+    tasty-quickcheck ^>=0.10,++flag dev+  description: Enable warnings as errors. Active on ci.+  default: False+  manual: True
+ src/Data/Automaton.hs view
@@ -0,0 +1,514 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE UndecidableInstances #-}++module Data.Automaton where++-- base+import Control.Applicative (Alternative (..))+import Control.Arrow+import Control.Category+import Control.Monad ((<=<))+import Control.Monad.Fix (MonadFix (mfix))+import Data.Coerce (coerce)+import Data.Function ((&))+import Data.Functor ((<&>))+import Data.Functor.Compose (Compose (..))+import Data.Maybe (fromMaybe)+import Data.Monoid (Last (..), Sum (..))+import Prelude hiding (id, (.))++-- mmorph+import Control.Monad.Morph (MFunctor (..))++-- transformers+import Control.Monad.Trans.Class+import Control.Monad.Trans.Reader++-- profunctors+import Data.Profunctor (Choice (..), Profunctor (..), Strong)+import Data.Profunctor.Strong (Strong (..))+import Data.Profunctor.Traversing++-- selective+import Control.Selective (Selective)++-- simple-affine-space+import Data.VectorSpace (VectorSpace (..))++-- align+import Data.Semialign (Align (..), Semialign (..))++-- automaton+import Data.Stream (StreamT (..), fixStream)+import Data.Stream.Internal (JointState (..))+import Data.Stream.Optimized (+  OptimizedStreamT (..),+  concatS,+  stepOptimizedStream,+ )+import Data.Stream.Optimized qualified as StreamOptimized+import Data.Stream.Result++-- * Constructing automata++{- | An effectful automaton in initial encoding.++* @m@: The monad in which the automaton performs side effects.+* @a@: The type of inputs the automaton constantly consumes.+* @b@: The type of outputs the automaton constantly produces.++An effectful automaton with input @a@ is the same as an effectful stream+with the additional effect of reading an input value @a@ on every step.+This is why automata are defined here as streams.++The API of automata follows that of streams ('StreamT' and 'OptimizedStreamT') closely.+The prominent addition in automata is now that they are instances of the 'Category', 'Arrow', 'Profunctor',+and related type classes.+This allows for more ways of creating or composing them.++For example, you can sequentially and parallely compose two automata:+@+automaton1 :: Automaton m a b+automaton2 :: Automaton m b c++sequentially :: Automaton m a c+sequentially = automaton1 >>> automaton2++parallely :: Automaton m (a, b) (b, c)+parallely = automaton1 *** automaton2+@+In sequential composition, the output of the first automaton is passed as input to the second one.+In parallel composition, both automata receive input simulataneously and process it independently.++Through the 'Arrow' type class, you can use 'arr' to create an automaton from a pure function,+and more generally use the arrow syntax extension to define automata.+-}+newtype Automaton m a b = Automaton {getAutomaton :: OptimizedStreamT (ReaderT a m) b}+  deriving newtype (Functor, Applicative, Alternative, Selective, Num, Fractional, Floating)++-- | Create an 'Automaton' from a state and a pure step function.+unfold ::+  (Applicative m) =>+  -- | The initial state+  s ->+  -- | The step function+  (a -> s -> Result s b) ->+  Automaton m a b+unfold state step = unfoldM state $ fmap pure <$> step++-- | Create an 'Automaton' from a state and an effectful step function.+unfoldM ::+  -- | The initial state+  s ->+  -- | The step function+  (a -> s -> m (Result s b)) ->+  Automaton m a b+unfoldM state step = Automaton $! Stateful $! StreamT {state, step = \s -> ReaderT $ \a -> step a s}++instance (Eq s, Floating s, VectorSpace v s, Applicative m) => VectorSpace (Automaton m a v) (Automaton m a s) where+  zeroVector = Automaton zeroVector+  Automaton s *^ Automaton v = coerce $ s *^ v+  Automaton v1 ^+^ Automaton v2 = coerce $ v1 ^+^ v2+  dot (Automaton s) (Automaton v) = coerce $ dot s v+  normalize (Automaton v) = coerce v++instance (Semialign m) => Semialign (Automaton m a) where+  align automaton1 automaton2 =+    Automaton $+      StreamOptimized.hoist' (ReaderT . getCompose) $+        align+          (StreamOptimized.hoist' (Compose . runReaderT) $ getAutomaton automaton1)+          (StreamOptimized.hoist' (Compose . runReaderT) $ getAutomaton automaton2)++instance (Align m) => Align (Automaton m a) where+  nil = constM nil++instance (Monad m) => Category (Automaton m) where+  id = Automaton $ Stateless ask+  {-# INLINE id #-}++  Automaton (Stateful (StreamT stateF0 stepF)) . Automaton (Stateful (StreamT stateG0 stepG)) =+    Automaton $!+      Stateful $!+        StreamT+          { state = JointState stateF0 stateG0+          , step = \(JointState stateF stateG) -> do+              Result stateG' b <- stepG stateG+              Result stateF' c <- lift $! runReaderT (stepF stateF) b+              return $! Result (JointState stateF' stateG') c+          }+  Automaton (Stateful (StreamT state0 step)) . Automaton (Stateless m) =+    Automaton $!+      Stateful $!+        StreamT+          { state = state0+          , step = \state -> do+              b <- m+              lift $! runReaderT (step state) b+          }+  Automaton (Stateless m) . Automaton (Stateful (StreamT state0 step)) =+    Automaton $!+      Stateful $!+        StreamT+          { state = state0+          , step = \state -> do+              Result state' b <- step state+              c <- lift $! runReaderT m b+              return $! Result state' c+          }+  Automaton (Stateless f) . Automaton (Stateless g) = Automaton $ Stateless $ ReaderT $ runReaderT f <=< runReaderT g+  {-# INLINE (.) #-}++instance (Monad m) => Arrow (Automaton m) where+  arr f = Automaton $! Stateless $! asks f+  {-# INLINE arr #-}++  first (Automaton (Stateful StreamT {state, step})) =+    Automaton $!+      Stateful $!+        StreamT+          { state+          , step = \s ->+              ReaderT+                ( \(b, d) ->+                    fmap (,d)+                      <$> runReaderT (step s) b+                )+          }+  first (Automaton (Stateless m)) = Automaton $ Stateless $ ReaderT $ \(b, d) -> (,d) <$> runReaderT m b+  {-# INLINE first #-}++instance (Monad m) => ArrowChoice (Automaton m) where+  Automaton (Stateful (StreamT stateL0 stepL)) +++ Automaton (Stateful (StreamT stateR0 stepR)) =+    Automaton $!+      Stateful $!+        StreamT+          { state = JointState stateL0 stateR0+          , step = \(JointState stateL stateR) ->+              ReaderT $!+                either+                  (runReaderT (mapResultState (`JointState` stateR) . fmap Left <$> stepL stateL))+                  (runReaderT (mapResultState (JointState stateL) . fmap Right <$> stepR stateR))+          }+  Automaton (Stateless m) +++ Automaton (Stateful (StreamT state0 step)) =+    Automaton $!+      Stateful $!+        StreamT+          { state = state0+          , step = \state ->+              ReaderT $!+                either+                  (runReaderT . fmap (Result state . Left) $ m)+                  (runReaderT . fmap (fmap Right) $ step state)+          }+  Automaton (Stateful (StreamT state0 step)) +++ Automaton (Stateless m) =+    Automaton $!+      Stateful $!+        StreamT+          { state = state0+          , step = \state ->+              ReaderT $!+                either+                  (runReaderT . fmap (fmap Left) $ step state)+                  (runReaderT . fmap (Result state . Right) $ m)+          }+  Automaton (Stateless mL) +++ Automaton (Stateless mR) =+    Automaton $+      Stateless $+        ReaderT $+          either+            (runReaderT . fmap Left $ mL)+            (runReaderT . fmap Right $ mR)+  {-# INLINE (+++) #-}++  left (Automaton (Stateful (StreamT {state, step}))) =+    Automaton $!+      Stateful $!+        StreamT+          { state+          , step = \s -> ReaderT $ either (fmap (fmap Left) . runReaderT (step s)) (pure . Result s . Right)+          }+  left (Automaton (Stateless ma)) = Automaton $! Stateless $! ReaderT $! either (fmap Left . runReaderT ma) (pure . Right)+  {-# INLINE left #-}++  right (Automaton (Stateful (StreamT {state, step}))) =+    Automaton $!+      Stateful $!+        StreamT+          { state+          , step = \s -> ReaderT $ either (pure . Result s . Left) (fmap (fmap Right) . runReaderT (step s))+          }+  right (Automaton (Stateless ma)) = Automaton $! Stateless $! ReaderT $! either (pure . Left) (fmap Right . runReaderT ma)+  {-# INLINE right #-}++-- | Caution, this can make your program hang. Try to use 'feedback' or 'unfold' where possible, or combine 'loop' with 'delay'.+instance (MonadFix m) => ArrowLoop (Automaton m) where+  loop (Automaton (Stateless ma)) = Automaton $! Stateless $! ReaderT (\b -> fst <$> mfix ((. snd) $ ($ b) $ curry $ runReaderT ma))+  loop (Automaton (Stateful (StreamT {state, step}))) =+    Automaton $!+      Stateful $!+        StreamT+          { state+          , step = \s -> ReaderT $ \b -> fmap fst <$> mfix ((. (snd . output)) $ ($ b) $ curry $ runReaderT $ step s)+          }+  {-# INLINE loop #-}++instance (Monad m, Alternative m) => ArrowZero (Automaton m) where+  zeroArrow = empty++instance (Monad m, Alternative m) => ArrowPlus (Automaton m) where+  (<+>) = (<|>)++-- | Consume an input and produce output effectfully, without keeping internal state+arrM :: (Functor m) => (a -> m b) -> Automaton m a b+arrM f = Automaton $! StreamOptimized.constM $! ReaderT f+{-# INLINE arrM #-}++-- | Produce output effectfully, without keeping internal state+constM :: (Functor m) => m b -> Automaton m a b+constM = arrM . const+{-# INLINE constM #-}++-- | Apply an arbitrary monad morphism to an automaton.+hoistS :: (Monad m) => (forall x. m x -> n x) -> Automaton m a b -> Automaton n a b+hoistS morph (Automaton automaton) = Automaton $ hoist (mapReaderT morph) automaton+{-# INLINE hoistS #-}++-- | Lift the monad of an automaton to a transformer.+liftS :: (MonadTrans t, Monad m, Functor (t m)) => Automaton m a b -> Automaton (t m) a b+liftS = hoistS lift+{-# INLINE liftS #-}++{- | Extend the internal state and feed back part of the output to the next input.++This is one of the fundamental ways to incorporate recursive dataflow in automata.+Given an automaton which consumes an additional input and produces an additional output,+the state of the automaton is extended by a further value.+This value is used as the additional input,+and the resulting additional output is stored in the internal state for the next step.+-}+feedback ::+  (Functor m) =>+  -- | The additional internal state+  c ->+  -- | The original automaton+  Automaton m (a, c) (b, c) ->+  Automaton m a b+feedback c (Automaton (Stateful StreamT {state, step})) =+  Automaton $!+    Stateful $!+      StreamT+        { state = JointState state c+        , step = \(JointState s c) -> ReaderT $ \a -> (\(Result s (b, c)) -> Result (JointState s c) b) <$> runReaderT (step s) (a, c)+        }+feedback state (Automaton (Stateless m)) =+  Automaton $!+    Stateful $!+      StreamT+        { state+        , step = \c -> ReaderT $ \a -> (\(b, c) -> Result c b) <$> runReaderT m (a, c)+        }+{-# INLINE feedback #-}++-- * Running automata++{- | Run one step of an automaton.++This consumes an input value, performs a side effect, and returns an updated automaton together with an output value.+-}+stepAutomaton :: (Functor m) => Automaton m a b -> a -> m (Result (Automaton m a b) b)+stepAutomaton (Automaton automatonT) a =+  runReaderT (stepOptimizedStream automatonT) a+    <&> mapResultState Automaton+{-# INLINE stepAutomaton #-}++{- | Run an automaton with trivial input and output indefinitely.++If the input and output of an automaton does not contain information,+all of its meaning is in its effects.+This function runs the automaton indefinitely.+Since it will never return with a value, this function also has no output (its output is void).+The only way it can return is if @m@ includes some effect of termination,+e.g. 'Maybe' or 'Either' could terminate with a 'Nothing' or 'Left' value,+or 'IO' can raise an exception.+-}+reactimate :: (Monad m) => Automaton m () () -> m void+reactimate (Automaton automaton) = StreamOptimized.reactimate $ hoist (`runReaderT` ()) automaton+{-# INLINE reactimate #-}++{- | Run an automaton with given input, for a given number of steps.++Especially for tests and batch processing,+it is useful to step an automaton with given input.+-}+embed ::+  (Monad m) =>+  -- | The automaton to run+  Automaton m a b ->+  -- | The input values+  [a] ->+  m [b]+embed (Automaton (Stateful StreamT {state, step})) = go state+  where+    go _s [] = return []+    go s (a : as) = do+      Result s' b <- runReaderT (step s) a+      (b :) <$> go s' as+embed (Automaton (Stateless m)) = mapM $ runReaderT m++-- * Modifying automata++-- | Change the output type and effect of an automaton without changing its state type.+withAutomaton :: (Functor m1, Functor m2) => (forall s. (a1 -> m1 (Result s b1)) -> (a2 -> m2 (Result s b2))) -> Automaton m1 a1 b1 -> Automaton m2 a2 b2+withAutomaton f = Automaton . StreamOptimized.mapOptimizedStreamT (ReaderT . f . runReaderT) . getAutomaton+{-# INLINE withAutomaton #-}++instance (Monad m) => Profunctor (Automaton m) where+  dimap f g Automaton {getAutomaton} = Automaton $ g <$> hoist (withReaderT f) getAutomaton+  lmap f Automaton {getAutomaton} = Automaton $ hoist (withReaderT f) getAutomaton+  rmap = fmap++instance (Monad m) => Choice (Automaton m) where+  right' = right+  left' = left++instance (Monad m) => Strong (Automaton m) where+  second' = second+  first' = first++-- | Step an automaton several steps at once, depending on how long the input is.+instance (Monad m) => Traversing (Automaton m) where+  wander f Automaton {getAutomaton = Stateful StreamT {state, step}} =+    Automaton+      { getAutomaton =+          Stateful+            StreamT+              { state+              , step =+                  step+                    & fmap runReaderT+                    & flip+                    & fmap ResultStateT+                    & f+                    & fmap getResultStateT+                    & flip+                    & fmap ReaderT+              }+      }+  wander f (Automaton (Stateless m)) = Automaton $ Stateless $ ReaderT $ f $ runReaderT m+  {-# INLINE wander #-}++-- | Only step the automaton if the input is 'Just'.+mapMaybeS :: (Monad m) => Automaton m a b -> Automaton m (Maybe a) (Maybe b)+mapMaybeS = traverse'++-- | Use an 'Automaton' with a variable amount of input.+traverseS :: (Monad m, Traversable f) => Automaton m a b -> Automaton m (f a) (f b)+traverseS = traverse'++-- | Like 'traverseS', discarding the output.+traverseS_ :: (Monad m, Traversable f) => Automaton m a b -> Automaton m (f a) ()+traverseS_ automaton = traverse' automaton >>> arr (const ())++{- | Launch arbitrarily many copies of the automaton in parallel.++* The copies of the automaton are launched on demand as the input lists grow.+* The n-th copy will always receive the n-th input.+* If the input list has length n, the n+1-th automaton copy will not be stepped.++Caution: Uses memory of the order of the largest list that was ever input during runtime.+-}+parallely :: (Applicative m) => Automaton m a b -> Automaton m [a] [b]+parallely Automaton {getAutomaton = Stateful stream} = Automaton $ Stateful $ parallely' stream+  where+    parallely' :: (Applicative m) => StreamT (ReaderT a m) b -> StreamT (ReaderT [a] m) [b]+    parallely' StreamT {state, step} = fixStream (JointState state) $ \fixstep jointState@(JointState s fixstate) -> ReaderT $ \case+      [] -> pure $! Result jointState []+      (a : as) -> apResult . fmap (:) <$> runReaderT (step s) a <*> runReaderT (fixstep fixstate) as+parallely Automaton {getAutomaton = Stateless f} = Automaton $ Stateless $ ReaderT $ traverse $ runReaderT f++-- | Given a transformation of streams, apply it to an automaton, without changing the input.+handleAutomaton_ :: (Monad m) => (forall m. (Monad m) => StreamT m a -> StreamT m b) -> Automaton m i a -> Automaton m i b+handleAutomaton_ f = Automaton . StreamOptimized.withOptimized f . getAutomaton++-- | Given a transformation of streams, apply it to an automaton. The input can be accessed through the 'ReaderT' effect.+handleAutomaton :: (Monad m) => (StreamT (ReaderT a m) b -> StreamT (ReaderT c n) d) -> Automaton m a b -> Automaton n c d+handleAutomaton f = Automaton . StreamOptimized.handleOptimized f . getAutomaton++-- | Buffer the output of an automaton. See 'Data.Stream.concatS'.+concatS :: (Monad m) => Automaton m () [b] -> Automaton m () b+concatS (Automaton automaton) = Automaton $ Data.Stream.Optimized.concatS automaton++-- * Examples++-- | Pass through a value unchanged, and perform a side effect depending on it+withSideEffect ::+  (Monad m) =>+  -- | For every value passing through the automaton, this function is called and the resulting side effect performed.+  (a -> m b) ->+  Automaton m a a+withSideEffect f = (id &&& arrM f) >>> arr fst++-- | Accumulate the input, output the accumulator.+accumulateWith ::+  (Monad m) =>+  -- | The accumulation function+  (a -> b -> b) ->+  -- | The initial accumulator+  b ->+  Automaton m a b+accumulateWith f state = unfold state $ \a b -> let b' = f a b in Result b' b'++-- | Like 'accumulateWith', with 'mappend' as the accumulation function.+mappendFrom :: (Monoid w, Monad m) => w -> Automaton m w w+mappendFrom = accumulateWith mappend++-- | Delay the input by one step.+delay ::+  (Applicative m) =>+  -- | The value to output on the first step+  a ->+  Automaton m a a+delay a0 = unfold a0 $ \aIn aState -> Result aIn aState++{- | Delay an automaton by one step by prepending one value to the output.++On the first step, the given initial output is returned.+On all subsequent steps, the automaton is stepped with the previous input.+-}+prepend :: (Monad m) => b -> Automaton m a b -> Automaton m a b+prepend b0 automaton = proc a -> do+  eab <- delay (Left b0) -< Right a+  case eab of+    Left b -> returnA -< b+    Right a -> automaton -< a++-- | Like 'mappendFrom', initialised at 'mempty'.+mappendS :: (Monoid w, Monad m) => Automaton m w w+mappendS = mappendFrom mempty++-- | Sum up all inputs so far, with an explicit initial value.+sumFrom :: (VectorSpace v s, Monad m) => v -> Automaton m v v+sumFrom = accumulateWith (^+^)++-- | Like 'sumFrom', initialised at 0.+sumS :: (Monad m, VectorSpace v s) => Automaton m v v+sumS = sumFrom zeroVector++-- | Sum up all inputs so far, initialised at 0.+sumN :: (Monad m, Num a) => Automaton m a a+sumN = arr Sum >>> mappendS >>> arr getSum++-- | Count the natural numbers, beginning at 1.+count :: (Num n, Monad m) => Automaton m a n+count = feedback 0 $! arr (\(_, n) -> let n' = n + 1 in (n', n'))++-- | Remembers the last 'Just' value, defaulting to the given initialisation value.+lastS :: (Monad m) => a -> Automaton m (Maybe a) a+lastS a = arr Last >>> mappendS >>> arr (getLast >>> fromMaybe a)
+ src/Data/Automaton/Final.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Data.Automaton.Final where++-- base+import Control.Applicative (Alternative)+import Control.Arrow+import Control.Category+import Prelude hiding (id, (.))++-- transformers+import Control.Monad.Trans.Reader++-- automaton+import Data.Automaton+import Data.Stream.Final qualified as StreamFinal+import Data.Stream.Optimized qualified as StreamOptimized++-- | Automata in final encoding.+newtype Final m a b = Final {getFinal :: StreamFinal.Final (ReaderT a m) b}+  deriving newtype (Functor, Applicative, Alternative)++instance (Monad m) => Category (Final m) where+  id = toFinal id+  f1 . f2 = toFinal $ fromFinal f1 . fromFinal f2++instance (Monad m) => Arrow (Final m) where+  arr = toFinal . arr+  first = toFinal . first . fromFinal++toFinal :: (Functor m) => Automaton m a b -> Final m a b+toFinal (Automaton automaton) = Final $ StreamOptimized.toFinal automaton++fromFinal :: Final m a b -> Automaton m a b+fromFinal Final {getFinal} = Automaton $ StreamOptimized.fromFinal getFinal
+ src/Data/Automaton/Trans/Except.hs view
@@ -0,0 +1,329 @@+{-# LANGUAGE Arrows #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE StrictData #-}++{- | An 'Automaton' in the 'ExceptT' monad can throw an exception to terminate.++This module defines several ways to throw exceptions,+and implements control flow by handling them.++The API is heavily inspired by @dunai@.+-}+module Data.Automaton.Trans.Except (+  module Data.Automaton.Trans.Except,+  module Control.Monad.Trans.Except,+)+where++-- base+import Control.Arrow (arr, returnA, (<<<), (>>>))+import Control.Category qualified as Category+import Data.Void (Void, absurd)++-- transformers+import Control.Monad.Trans.Except (ExceptT (..), runExceptT, throwE)+import Control.Monad.Trans.Maybe (MaybeT, runMaybeT)+import Control.Monad.Trans.Reader++-- selective+import Control.Selective (Selective)++-- mmorph+import Control.Monad.Morph++-- automaton+import Data.Automaton (+  Automaton (..),+  arrM,+  constM,+  count,+  feedback,+  hoistS,+  liftS,+  mapMaybeS,+  reactimate,+ )+import Data.Automaton.Trans.Except.Internal+import Data.Stream.Except hiding (safely)+import Data.Stream.Except qualified as StreamExcept+import Data.Stream.Optimized (mapOptimizedStreamT)+import Data.Stream.Optimized qualified as StreamOptimized++-- * Throwing exceptions++-- | Throw the exception 'e' whenever the function evaluates to 'True'.+throwOnCond :: (Monad m) => (a -> Bool) -> e -> Automaton (ExceptT e m) a a+throwOnCond cond e = proc a ->+  if cond a+    then throwS -< e+    else returnA -< a++{- | Throws the exception when the input is 'True'.++Variant of 'throwOnCond' for Kleisli arrows.+-}+throwOnCondM :: (Monad m) => (a -> m Bool) -> e -> Automaton (ExceptT e m) a a+throwOnCondM cond e = proc a -> do+  b <- arrM (lift . cond) -< a+  if b+    then throwS -< e+    else returnA -< a++-- | Throw the exception when the input is 'True'.+throwOn :: (Monad m) => e -> Automaton (ExceptT e m) Bool ()+throwOn e = proc b -> throwOn' -< (b, e)++-- | Variant of 'throwOn', where the exception may change every tick.+throwOn' :: (Monad m) => Automaton (ExceptT e m) (Bool, e) ()+throwOn' = proc (b, e) ->+  if b+    then throwS -< e+    else returnA -< ()++{- | When the input is @Just e@, throw the exception @e@.++This does not output any data since it terminates on the first nontrivial input.+-}+throwMaybe :: (Monad m) => Automaton (ExceptT e m) (Maybe e) (Maybe void)+throwMaybe = mapMaybeS throwS++{- | Immediately throw the incoming exception.++This is useful to combine with 'ArrowChoice',+e.g. with @if@ and @case@ expressions in Arrow syntax.+-}+throwS :: (Monad m) => Automaton (ExceptT e m) e a+throwS = arrM throwE++-- | Immediately throw the given exception.+throw :: (Monad m) => e -> Automaton (ExceptT e m) a b+throw = constM . throwE++-- | Do not throw an exception.+pass :: (Monad m) => Automaton (ExceptT e m) a a+pass = Category.id++{- | Converts an 'Automaton' in 'MaybeT' to an 'Automaton' in 'ExceptT'.++Whenever 'Nothing' is thrown, throw @()@ instead.+-}+maybeToExceptS ::+  (Functor m, Monad m) =>+  Automaton (MaybeT m) a b ->+  Automaton (ExceptT () m) a b+maybeToExceptS = hoistS (ExceptT . (maybe (Left ()) Right <$>) . runMaybeT)++-- * Catching exceptions++{- | Catch an exception in an 'Automaton'.++As soon as an exception occurs, switch to a new 'Automaton',+the exception handler, based on the exception value.++For exception catching where the handler can throw further exceptions, see 'AutomatonExcept' further below.+-}+catchS :: (Monad m) => Automaton (ExceptT e m) a b -> (e -> Automaton m a b) -> Automaton m a b+catchS automaton f = safely $ do+  e <- try automaton+  safe $ f e++-- | Similar to Yampa's delayed switching. Loses a @b@ in case of an exception.+untilE ::+  (Monad m) =>+  Automaton m a b ->+  Automaton m b (Maybe e) ->+  Automaton (ExceptT e m) a b+untilE automaton automatone = proc a -> do+  b <- liftS automaton -< a+  me <- liftS automatone -< b+  inExceptT -< ExceptT $ return $ maybe (Right b) Left me++{- | Escape an 'ExceptT' layer by outputting the exception whenever it occurs.++If an exception occurs, the current state is is tested again on the next input.+-}+exceptS :: (Functor m, Monad m) => Automaton (ExceptT e m) a b -> Automaton m a (Either e b)+exceptS = Automaton . StreamOptimized.exceptS . mapOptimizedStreamT commuteReader . getAutomaton++{- | Embed an 'ExceptT' value inside the 'Automaton'.++Whenever the input value is an ordinary value, it is passed on. If it is an exception, it is raised.+-}+inExceptT :: (Monad m) => Automaton (ExceptT e m) (ExceptT e m a) a+inExceptT = arrM id++{- | In case an exception occurs in the first argument, replace the exception+by the second component of the tuple.+-}+tagged :: (Monad m) => Automaton (ExceptT e1 m) a b -> Automaton (ExceptT e2 m) (a, e2) b+tagged automaton = runAutomatonExcept $ try (automaton <<< arr fst) *> (snd <$> currentInput)++-- * Monad interface for Exception Automatons++{- | An 'Automaton' that can terminate with an exception.++* @m@: The monad that the 'Automaton' may take side effects in.+* @a@: The type of input values the stream constantly consumes.+* @b@: The type of output values the stream constantly produces.+* @e@: The type of exceptions with which the stream can terminate.++This type is useful because it is a monad in the /exception type/ @e@.++  * 'return' corresponds to throwing an exception immediately.+  * '>>=' is exception handling: The first value throws an exception, while+    the Kleisli arrow handles the exception and produces a new signal+    function, which can throw exceptions in a different type.++Consider this example:+@+automaton :: AutomatonExcept a b m e1+f :: e1 -> AutomatonExcept a b m e2++example :: AutomatonExcept a b m e2+example = automaton >>= f+@++Here, @automaton@ produces output values of type @b@ until an exception @e1@ occurs.+The function @f@ is called on the exception value and produces a continuation automaton+which is then executed (until it possibly throws an exception @e2@ itself).++The generality of the monad interface comes at a cost, though.+In order to achieve higher performance, you should use the 'Monad' interface sparingly.+Whenever you can express the same control flow using 'Functor', 'Applicative', 'Selective',+or just the '(>>)' operator, you should do this.+The encoding of the internal state type will be much more efficiently optimized.++The reason for this is that in an expression @ma >>= f@,+the type of @f@ is @e1 -> AutomatonExcept a b m e2@,+which implies that the state of the 'AutomatonExcept' produced isn't known at compile time,+and thus GHC cannot optimize the automaton.+But often the full expressiveness of '>>=' isn't necessary, and in these cases,+a much faster automaton is produced by using 'Functor', 'Applicative' and 'Selective'.++Note: By "exceptions", we mean an 'ExceptT' transformer layer, not 'IO' exceptions.+-}+newtype AutomatonExcept a b m e = AutomatonExcept {getAutomatonExcept :: StreamExcept b (ReaderT a m) e}+  deriving newtype (Functor, Applicative, Selective, Monad)++instance MonadTrans (AutomatonExcept a b) where+  lift = AutomatonExcept . lift . lift++instance MFunctor (AutomatonExcept a b) where+  hoist morph = AutomatonExcept . hoist (mapReaderT morph) . getAutomatonExcept++runAutomatonExcept :: (Monad m) => AutomatonExcept a b m e -> Automaton (ExceptT e m) a b+runAutomatonExcept = Automaton . hoist commuteReaderBack . runStreamExcept . getAutomatonExcept++{- | Execute an 'Automaton' in 'ExceptT' until it raises an exception.++Typically used to enter the monad context of 'AutomatonExcept'.+-}+try :: (Monad m) => Automaton (ExceptT e m) a b -> AutomatonExcept a b m e+try = AutomatonExcept . InitialExcept . hoist commuteReader . getAutomaton++{- | Immediately throw the current input as an exception.++Useful inside 'AutomatonExcept' if you don't want to advance a further step in execution,+but first see what the current input is before continuing.+-}+currentInput :: (Monad m) => AutomatonExcept e b m e+currentInput = try throwS++{- | If no exception can occur, the 'Automaton' can be executed without the 'ExceptT'+layer.++Used to exit the 'AutomatonExcept' context, often in combination with 'safe':++@+automaton = safely $ do+  e <- try someAutomaton+  once $ \input -> putStrLn $ "Whoops, something happened when receiving input " ++ show input ++ ": " ++ show e ++ ", but I'll continue now."+  safe fallbackAutomaton+@+-}+safely :: (Monad m) => AutomatonExcept a b m Void -> Automaton m a b+safely = Automaton . StreamExcept.safely . getAutomatonExcept++{- | An 'Automaton' without an 'ExceptT' layer never throws an exception, and can+thus have an arbitrary exception type.++In particular, the exception type can be 'Void', so it can be used as the last statement in an 'AutomatonExcept' @do@-block.+See 'safely' for an example.+-}+safe :: (Monad m) => Automaton m a b -> AutomatonExcept a b m e+safe = try . liftS++{- | Inside the 'AutomatonExcept' monad, execute an action of the wrapped monad.+This passes the last input value to the action, but doesn't advance a tick.+-}+once :: (Monad m) => (a -> m e) -> AutomatonExcept a b m e+once f = AutomatonExcept $ InitialExcept $ StreamOptimized.constM $ ExceptT $ ReaderT $ fmap Left <$> f++-- | Variant of 'once' without input.+once_ :: (Monad m) => m e -> AutomatonExcept a b m e+once_ = once . const++-- | Advances a single tick with the given Kleisli arrow, and then throws an exception.+step :: (Monad m) => (a -> m (b, e)) -> AutomatonExcept a b m e+step f = try $ proc a -> do+  n <- count -< ()+  (b, e) <- arrM (lift . f) -< a+  _ <- throwOn' -< (n > (1 :: Int), e)+  returnA -< b++-- | Advances a single tick outputting the value, and then throws '()'.+step_ :: (Monad m) => b -> AutomatonExcept a b m ()+step_ b = step $ const $ return (b, ())++{- | Converts a list to an 'AutomatonExcept', which outputs an element of the list at+each step, throwing '()' when the list ends.+-}+listToAutomatonExcept :: (Monad m) => [b] -> AutomatonExcept a b m ()+listToAutomatonExcept = mapM_ step_++-- * Utilities definable in terms of 'AutomatonExcept'++{- | Extract an 'Automaton' from a monadic action.++Runs a monadic action that produces an 'Automaton' on the first step,+and then runs result for all further inputs (including the first one).+-}+performOnFirstSample :: (Monad m) => m (Automaton m a b) -> Automaton m a b+performOnFirstSample mAutomaton = safely $ do+  automaton <- once_ mAutomaton+  safe automaton++-- | 'reactimate's an 'AutomatonExcept' until it throws an exception.+reactimateExcept :: (Monad m) => AutomatonExcept () () m e -> m e+reactimateExcept ae = fmap (either id absurd) $ runExceptT $ reactimate $ runAutomatonExcept ae++-- | 'reactimate's an 'Automaton' until it returns 'True'.+reactimateB :: (Monad m) => Automaton m () Bool -> m ()+reactimateB ae = reactimateExcept $ try $ liftS ae >>> throwOn ()++{- | Run the first 'Automaton' until the second value in the output tuple is @Just c@,+then start the second automaton, discarding the current output @b@.++This is analogous to Yampa's+[@switch@](https://hackage.haskell.org/package/Yampa/docs/FRP-Yampa-Switches.html#v:switch),+with 'Maybe' instead of @Event@.+-}+switch :: (Monad m) => Automaton m a (b, Maybe c) -> (c -> Automaton m a b) -> Automaton m a b+switch automaton = catchS $ proc a -> do+  (b, me) <- liftS automaton -< a+  throwMaybe -< me+  returnA -< b++{- | Run the first 'Automaton' until the second value in the output tuple is @Just c@,+then start the second automaton one step later (after the current @b@ has been output).++Analog to Yampa's+[@dswitch@](https://hackage.haskell.org/package/Yampa/docs/FRP-Yampa-Switches.html#v:dSwitch),+with 'Maybe' instead of @Event@.+-}+dSwitch :: (Monad m) => Automaton m a (b, Maybe c) -> (c -> Automaton m a b) -> Automaton m a b+dSwitch sf = catchS $ feedback Nothing $ proc (a, me) -> do+  throwMaybe -< me+  liftS sf -< a
+ src/Data/Automaton/Trans/Except/Internal.hs view
@@ -0,0 +1,11 @@+module Data.Automaton.Trans.Except.Internal where++-- transformers+import Control.Monad.Trans.Except (ExceptT (..), runExceptT)+import Control.Monad.Trans.Reader++commuteReader :: ReaderT r (ExceptT e m) a -> ExceptT e (ReaderT r m) a+commuteReader = ExceptT . ReaderT . fmap runExceptT . runReaderT++commuteReaderBack :: ExceptT e (ReaderT r m) a -> ReaderT r (ExceptT e m) a+commuteReaderBack = ReaderT . fmap ExceptT . runReaderT . runExceptT
+ src/Data/Automaton/Trans/Maybe.hs view
@@ -0,0 +1,120 @@+-- | An 'Automaton' with 'Maybe' or 'MaybeT' in its monad stack can terminate execution at any step.+module Data.Automaton.Trans.Maybe (+  module Data.Automaton.Trans.Maybe,+  module Control.Monad.Trans.Maybe,+  maybeToExceptS,+)+where++-- base+import Control.Arrow (arr, returnA, (>>>))++-- transformers+import Control.Monad.Trans.Maybe hiding (+  liftCallCC,+  liftCatch,+  liftListen,+  liftPass,+ )++-- automaton+import Data.Automaton (Automaton, arrM, constM, hoistS, liftS)+import Data.Automaton.Trans.Except (+  ExceptT,+  exceptS,+  listToAutomatonExcept,+  maybeToExceptS,+  reactimateExcept,+  runAutomatonExcept,+  runExceptT,+  safe,+  safely,+  try,+ )++-- * Throwing 'Nothing' as an exception ("exiting")++-- | Throw the exception immediately.+exit :: (Monad m) => Automaton (MaybeT m) a b+exit = constM $ MaybeT $ return Nothing++-- | Throw the exception when the condition becomes true on the input.+exitWhen :: (Monad m) => (a -> Bool) -> Automaton (MaybeT m) a a+exitWhen condition = proc a -> do+  _ <- exitIf -< condition a+  returnA -< a++-- | Exit when the incoming value is 'True'.+exitIf :: (Monad m) => Automaton (MaybeT m) Bool ()+exitIf = proc condition ->+  if condition+    then exit -< ()+    else returnA -< ()++-- | @Just a@ is passed along, 'Nothing' causes the whole 'Automaton' to exit.+maybeExit :: (Monad m) => Automaton (MaybeT m) (Maybe a) a+maybeExit = inMaybeT++-- | Embed a 'Maybe' value in the 'MaybeT' layer. Identical to 'maybeExit'.+inMaybeT :: (Monad m) => Automaton (MaybeT m) (Maybe a) a+inMaybeT = arrM $ MaybeT . return++-- * Catching Maybe exceptions++-- | Run the first automaton until the second one produces 'True' from the output of the first.+untilMaybe :: (Monad m) => Automaton m a b -> Automaton m b Bool -> Automaton (MaybeT m) a b+untilMaybe automaton cond = proc a -> do+  b <- liftS automaton -< a+  c <- liftS cond -< b+  inMaybeT -< if c then Nothing else Just b++{- | When an exception occurs in the first 'automaton', the second 'automaton' is executed+from there.+-}+catchMaybe ::+  (Functor m, Monad m) =>+  Automaton (MaybeT m) a b ->+  Automaton m a b ->+  Automaton m a b+catchMaybe automaton1 automaton2 = safely $ try (maybeToExceptS automaton1) >> safe automaton2++-- * Converting to and from 'MaybeT'++-- | Convert exceptions into `Nothing`, discarding the exception value.+exceptToMaybeS ::+  (Functor m, Monad m) =>+  Automaton (ExceptT e m) a b ->+  Automaton (MaybeT m) a b+exceptToMaybeS =+  hoistS $ MaybeT . fmap (either (const Nothing) Just) . runExceptT++{- | Converts a list to an 'Automaton' in 'MaybeT', which outputs an element of the+list at each step, throwing 'Nothing' when the list ends.+-}+listToMaybeS :: (Functor m, Monad m) => [b] -> Automaton (MaybeT m) a b+listToMaybeS = exceptToMaybeS . runAutomatonExcept . listToAutomatonExcept++-- * Running 'MaybeT'++{- | Remove the 'MaybeT' layer by outputting 'Nothing' when the exception occurs.++The current state is then tested again on the next input.+-}+runMaybeS :: (Functor m, Monad m) => Automaton (MaybeT m) a b -> Automaton m a (Maybe b)+runMaybeS automaton = exceptS (maybeToExceptS automaton) >>> arr eitherToMaybe+  where+    eitherToMaybe (Left ()) = Nothing+    eitherToMaybe (Right b) = Just b++-- | 'reactimate's an 'Automaton' in the 'MaybeT' monad until it throws 'Nothing'.+reactimateMaybe ::+  (Functor m, Monad m) =>+  Automaton (MaybeT m) () () ->+  m ()+reactimateMaybe automaton = reactimateExcept $ try $ maybeToExceptS automaton++{- | Run an 'Automaton' fed from a list, discarding results. Useful when one needs to+combine effects and streams (i.e., for testing purposes).+-}+embed_ :: (Functor m, Monad m) => Automaton m a () -> [a] -> m ()+embed_ automaton as = reactimateMaybe $ listToMaybeS as >>> liftS automaton
+ src/Data/Automaton/Trans/RWS.hs view
@@ -0,0 +1,40 @@+{- | This module combines the wrapping and running functions for the 'Reader',+'Writer' and 'State' monad layers in a single layer.++It is based on the /strict/ 'RWS' monad 'Control.Monad.Trans.RWS.Strict',+so when combining it with other modules such as @mtl@'s, the strict version+has to be included, i.e. 'Control.Monad.RWS.Strict' instead of+'Control.Monad.RWS' or 'Control.Monad.RWS.Lazy'.+-}+module Data.Automaton.Trans.RWS (+  module Data.Automaton.Trans.RWS,+  module Control.Monad.Trans.RWS.Strict,+)+where++-- transformers+import Control.Monad.Trans.RWS.Strict hiding (liftCallCC, liftCatch)++-- automaton+import Data.Automaton (Automaton, withAutomaton)+import Data.Stream.Result (Result (..))++-- * 'RWS' (Reader-Writer-State) monad++-- | Wrap an 'Automaton' with explicit state variables in 'RWST' monad transformer.+rwsS ::+  (Functor m, Monad m, Monoid w) =>+  Automaton m (r, s, a) (w, s, b) ->+  Automaton (RWST r w s m) a b+rwsS = withAutomaton $ \f a -> RWST $ \r s ->+  (\(Result c (w, s', b)) -> (Result c b, s', w))+    <$> f (r, s, a)++-- | Run the 'RWST' layer by making the state variables explicit.+runRWSS ::+  (Functor m, Monad m, Monoid w) =>+  Automaton (RWST r w s m) a b ->+  Automaton m (r, s, a) (w, s, b)+runRWSS = withAutomaton $ \f (r, s, a) ->+  (\(Result c b, s', w) -> Result c (w, s', b))+    <$> runRWST (f a) r s
+ src/Data/Automaton/Trans/Random.hs view
@@ -0,0 +1,94 @@+{- | An 'Automaton's in a monad supporting random number generation (i.e.+having the 'RandT' layer in its stack) can be run.++Running means supplying an initial random number generator,+where the update of the generator at every random number generation is already taken care of.++Under the hood, 'RandT' is basically just 'StateT', with the current random+number generator as mutable state.+-}+module Data.Automaton.Trans.Random (+  runRandS,+  evalRandS,+  getRandomS,+  getRandomsS,+  getRandomRS,+  getRandomRS_,+  getRandomsRS,+  getRandomsRS_,+)+where++-- base+import Control.Arrow (arr, (>>>))++-- MonadRandom+import Control.Monad.Random (+  MonadRandom,+  RandT,+  Random,+  RandomGen,+  getRandom,+  getRandomR,+  getRandomRs,+  getRandoms,+  runRandT,+ )++-- automaton+import Data.Automaton (Automaton, arrM, constM, hoistS)+import Data.Automaton.Trans.State (StateT (..), runStateS_)++-- * Creating random values++-- | Create a stream of random values.+getRandomS :: (MonadRandom m, Random b) => Automaton m a b+getRandomS = constM getRandom++-- | Create a stream of lists of random values.+getRandomsS :: (MonadRandom m, Random b) => Automaton m a [b]+getRandomsS = constM getRandoms++-- | Create a stream of random values in a given fixed range.+getRandomRS :: (MonadRandom m, Random b) => (b, b) -> Automaton m a b+getRandomRS range = constM $ getRandomR range++{- | Create a stream of random values in a given range, where the range is+specified on every tick.+-}+getRandomRS_ :: (MonadRandom m, Random b) => Automaton m (b, b) b+getRandomRS_ = arrM getRandomR++-- | Create a stream of lists of random values in a given fixed range.+getRandomsRS :: (MonadRandom m, Random b) => (b, b) -> Automaton m a [b]+getRandomsRS range = constM $ getRandomRs range++{- | Create a stream of lists of random values in a given range, where the+range is specified on every tick.+-}+getRandomsRS_ :: (MonadRandom m, Random b) => Automaton m (b, b) [b]+getRandomsRS_ = arrM getRandomRs++-- * Running automata with random effects++{- | Run an 'Automaton' in the 'RandT' random number monad transformer by supplying+an initial random generator. Updates and outputs the generator every step.+-}+runRandS ::+  (RandomGen g, Functor m, Monad m) =>+  Automaton (RandT g m) a b ->+  -- | The initial random number generator.+  g ->+  Automaton m a (g, b)+runRandS = runStateS_ . hoistS (StateT . runRandT)++{- | Evaluate an 'Automaton' in the 'RandT' transformer, i.e. extract possibly random+values by supplying an initial random generator. Updates the generator every+step but discards the generator.+-}+evalRandS ::+  (RandomGen g, Functor m, Monad m) =>+  Automaton (RandT g m) a b ->+  g ->+  Automaton m a b+evalRandS automaton g = runRandS automaton g >>> arr snd
+ src/Data/Automaton/Trans/Reader.hs view
@@ -0,0 +1,43 @@+{- | An 'Automaton' with a 'ReaderT' layer has an extra input.++This module converts between explicit automata inputs and implicit 'ReaderT' inputs.+-}+module Data.Automaton.Trans.Reader (+  module Control.Monad.Trans.Reader,+  readerS,+  runReaderS,+  runReaderS_,+)+where++-- base+import Control.Arrow (arr, (>>>))++-- transformers+import Control.Monad.Trans.Reader++-- automaton+import Data.Automaton (Automaton, withAutomaton)++-- * Reader 'Automaton' running and wrapping++{- | Convert an explicit 'Automaton' input into an environment in the 'ReaderT' monad transformer.++This is the opposite of 'runReaderS'.+-}+readerS :: (Monad m) => Automaton m (r, a) b -> Automaton (ReaderT r m) a b+readerS = withAutomaton $ \f a -> ReaderT $ \r -> f (r, a)+{-# INLINE readerS #-}++{- | Convert an implicit 'ReaderT' environment into an explicit 'Automaton' input.++This is the opposite of 'readerS'.+-}+runReaderS :: (Monad m) => Automaton (ReaderT r m) a b -> Automaton m (r, a) b+runReaderS = withAutomaton $ \f (r, a) -> runReaderT (f a) r+{-# INLINE runReaderS #-}++-- | Eliminate a 'ReaderT' layer by providing its environment statically.+runReaderS_ :: (Monad m) => Automaton (ReaderT s m) a b -> s -> Automaton m a b+runReaderS_ automaton s = arr (s,) >>> runReaderS automaton+{-# INLINE runReaderS_ #-}
+ src/Data/Automaton/Trans/State.hs view
@@ -0,0 +1,69 @@+{- | Handle a global 'StateT' layer in an 'Automaton'.++A global state can be hidden by an automaton by making it an internal state.++This module is based on the /strict/ state monad 'Control.Monad.Trans.State.Strict',+so when combining it with other modules such as @mtl@'s,+the strict version has to be included, i.e. 'Control.Monad.State.Strict'+instead of 'Control.Monad.State' or 'Control.Monad.State.Lazy'.+-}+module Data.Automaton.Trans.State (+  module Control.Monad.Trans.State.Strict,+  stateS,+  runStateS,+  runStateS_,+  runStateS__,+)+where++-- base+import Control.Arrow (arr, (>>>))+import Data.Tuple (swap)++-- transformers+import Control.Monad.Trans.State.Strict++-- automaton+import Data.Automaton (Automaton, feedback, withAutomaton)+import Data.Stream.Result (Result (..))++-- * 'State' 'Automaton' running and wrapping++{- | Convert from explicit states to the 'StateT' monad transformer.++The original automaton is interpreted to take a state as input and return the updated state as output.++This is the opposite of 'runStateS'.+-}+stateS :: (Functor m, Monad m) => Automaton m (s, a) (s, b) -> Automaton (StateT s m) a b+stateS = withAutomaton $ \f a -> StateT $ \s ->+  (\(Result s' (s, b)) -> (Result s' b, s))+    <$> f (s, a)++{- | Make the state transition in 'StateT' explicit as 'Automaton' inputs and outputs.++This is the opposite of 'stateS'.+-}+runStateS :: (Functor m, Monad m) => Automaton (StateT s m) a b -> Automaton m (s, a) (s, b)+runStateS = withAutomaton $ \f (s, a) ->+  (\(Result s' b, s) -> Result s' (s, b))+    <$> runStateT (f a) s++{- | Convert global state to internal state of an 'Automaton'.++The current state is output on every step.+-}+runStateS_ ::+  (Functor m, Monad m) =>+  -- | An automaton with a global state effect+  Automaton (StateT s m) a b ->+  -- | The initial global state+  s ->+  Automaton m a (s, b)+runStateS_ automaton s =+  feedback s $+    arr swap >>> runStateS automaton >>> arr (\(s', b) -> ((s', b), s'))++-- | Like 'runStateS_', but don't output the current state.+runStateS__ :: (Functor m, Monad m) => Automaton (StateT s m) a b -> s -> Automaton m a b+runStateS__ automaton s = runStateS_ automaton s >>> arr snd
+ src/Data/Automaton/Trans/Writer.hs view
@@ -0,0 +1,42 @@+{- | An 'Automaton' with a 'WriterT' layer outputs an extra monoid value on every step.++It is based on the /strict/ writer monad 'Control.Monad.Trans.Writer.Strict',+so when combining it with other modules such as @mtl@'s,+the strict version has to be included, i.e. 'Control.Monad.Writer.Strict'+instead of 'Control.Monad.Writer' or 'Control.Monad.Writer.Lazy'.+-}+module Data.Automaton.Trans.Writer (+  module Control.Monad.Trans.Writer.Strict,+  writerS,+  runWriterS,+)+where++-- transformers+import Control.Monad.Trans.Writer.Strict hiding (liftCallCC, liftCatch, pass)++-- automaton+import Data.Automaton (Automaton, withAutomaton)+import Data.Stream.Result (Result (Result))++{- | Convert an extra log output into a 'WriterT' effect.++This is the opposite of 'runWriterS'.+-}+writerS ::+  (Functor m, Monad m, Monoid w) =>+  Automaton m a (w, b) ->+  Automaton (WriterT w m) a b+writerS = withAutomaton $ \f a -> WriterT $ (\(Result s (w, b)) -> (Result s b, w)) <$> f a++{- | Convert a 'WriterT' effect into an extra log output.++This is the opposite of 'writerS'.+-}+runWriterS ::+  (Functor m, Monad m) =>+  Automaton (WriterT w m) a b ->+  Automaton m a (w, b)+runWriterS = withAutomaton $ \f a ->+  (\(Result s b, w) -> Result s (w, b))+    <$> runWriterT (f a)
+ src/Data/Stream.hs view
@@ -0,0 +1,418 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE UndecidableInstances #-}++module Data.Stream where++-- base+import Control.Applicative (Alternative (..), Applicative (..), liftA2)+import Control.Monad ((<$!>))+import Data.Bifunctor (bimap)+import Data.Monoid (Ap (..))+import Prelude hiding (Applicative (..))++-- transformers+import Control.Monad.Trans.Class+import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE, withExceptT)++-- mmorph+import Control.Monad.Morph (MFunctor (hoist))++-- simple-affine-space+import Data.VectorSpace (VectorSpace (..))++-- selective+import Control.Selective++-- these+import Data.These (These (..))++-- semialign+import Data.Align++-- automaton+import Data.Stream.Internal+import Data.Stream.Result++-- * Creating streams++{- | Effectful streams in initial encoding.++A stream consists of an internal state @s@, and a step function.+This step can make use of an effect in @m@ (which is often a monad),+alter the state, and return a result value.+Its semantics is continuously outputting values of type @b@,+while performing side effects in @m@.++An initial encoding was chosen instead of the final encoding known from e.g. @list-transformer@, @dunai@, @machines@, @streaming@, ...,+because the initial encoding is much more amenable to compiler optimizations+than the final encoding, which is:++@+  data StreamFinalT m b = StreamFinalT (m (b, StreamFinalT m b))+@++When two streams are composed, GHC can often optimize the combined step function,+resulting in a faster streams than what the final encoding can ever achieve,+because the final encoding has to step through every continuation.+Put differently, the compiler can perform static analysis on the state types of initially encoded state machines,+while the final encoding knows its state only at runtime.++This performance gain comes at a peculiar cost:+Recursive definitions /of/ streams are not possible, e.g. an equation like:+@+  fixA stream = stream <*> fixA stream+@+This is impossible since the stream under definition itself appears in the definition body,+and thus the internal /state type/ would be recursively defined, which GHC doesn't allow:+Type level recursion is not supported in existential types.+An stream defined thusly will typically hang and/or leak memory, trying to build up an infinite type at runtime.++It is nevertheless possible to define streams recursively, but one needs to first identify the recursive definition of its /state type/.+Then for the greatest generality, 'fixStream' and 'fixStream'' can be used, and some special cases are covered by functions+such as 'fixA', 'Data.Automaton.parallely', 'many' and 'some'.+-}+data StreamT m a = forall s.+  StreamT+  { state :: s+  -- ^ The internal state of the stream+  , step :: s -> m (Result s a)+  -- ^ Stepping a stream by one tick means:+  --   1. performing a side effect in @m@+  --   2. updating the internal state @s@+  --   3. outputting a value of type @a@+  }++-- | Initialise with an internal state, update the state and produce output without side effects.+unfold :: (Applicative m) => s -> (s -> Result s a) -> StreamT m a+unfold state step =+  StreamT+    { state+    , step = pure . step+    }++-- | Like 'unfold', but output the current state.+unfold_ :: (Applicative m) => s -> (s -> s) -> StreamT m s+unfold_ state step = unfold state $ \s -> let s' = step s in Result s' s'++-- | Constantly perform the same effect, without remembering a state.+constM :: (Functor m) => m a -> StreamT m a+constM ma = StreamT () $ const $ Result () <$> ma+{-# INLINE constM #-}++instance (Functor m) => Functor (StreamT m) where+  fmap f StreamT {state, step} = StreamT state $! fmap (fmap f) <$> step+  {-# INLINE fmap #-}++-- | 'pure' forever returns the same value, '(<*>)' steps two streams synchronously.+instance (Applicative m) => Applicative (StreamT m) where+  pure = constM . pure+  {-# INLINE pure #-}++  StreamT stateF0 stepF <*> StreamT stateA0 stepA =+    StreamT (JointState stateF0 stateA0) (\(JointState stateF stateA) -> apResult <$> stepF stateF <*> stepA stateA)+  {-# INLINE (<*>) #-}++deriving via Ap (StreamT m) a instance (Applicative m, Num a) => Num (StreamT m a)++instance (Applicative m, Fractional a) => Fractional (StreamT m a) where+  fromRational = pure . fromRational+  recip = fmap recip++instance (Applicative m, Floating a) => Floating (StreamT m a) where+  pi = pure pi+  exp = fmap exp+  log = fmap log+  sin = fmap sin+  cos = fmap cos+  asin = fmap asin+  acos = fmap acos+  atan = fmap atan+  sinh = fmap sinh+  cosh = fmap cosh+  asinh = fmap asinh+  acosh = fmap acosh+  atanh = fmap atanh++instance (VectorSpace v s, Eq s, Floating s, Applicative m) => VectorSpace (StreamT m v) (StreamT m s) where+  zeroVector = pure zeroVector+  (*^) = liftA2 (*^)+  (^+^) = liftA2 (^+^)+  dot = liftA2 dot+  normalize = fmap normalize++{- | 'empty' just performs 'empty' in the underlying monad @m@.+  @s1 '<|>' s2@ starts in an undecided state,+  and explores the possibilities of continuing in @s1@ or @s2@+  on the first tick, using the underlying @m@.+-}+instance (Alternative m) => Alternative (StreamT m) where+  empty = constM empty+  {-# INLINE empty #-}++  StreamT stateL0 stepL <|> StreamT stateR0 stepR =+    StreamT+      { state = Undecided+      , step = \case+          Undecided -> (mapResultState DecideL <$> stepL stateL0) <|> (mapResultState DecideR <$> stepR stateR0)+          DecideL stateL -> mapResultState DecideL <$> stepL stateL+          DecideR stateR -> mapResultState DecideR <$> stepR stateR+      }+  {-# INLINE (<|>) #-}++  many StreamT {state, step} = fixStream'+    (const NotStarted)+    $ \fixstate fixstep -> \case+      NotStarted -> ((\(Result s' a) (Result ss' as) -> Result (Ongoing ss' s') $ a : as) <$> step state <*> fixstep fixstate) <|> pure (Result Finished [])+      Finished -> pure $! Result Finished []+      Ongoing ss s -> (\(Result s' a) (Result ss' as) -> Result (Ongoing ss' s') $ a : as) <$> step s <*> fixstep ss+  {-# INLINE many #-}++  some stream = (:) <$> stream <*> many stream+  {-# INLINE some #-}++instance MFunctor StreamT where+  hoist = hoist'+  {-# INLINE hoist #-}++{- | Hoist a stream along a monad morphism, by applying said morphism to the step function.++This is like @mmorph@'s 'hoist', but it doesn't require a 'Monad' constraint on @m2@.+-}+hoist' :: (forall x. m1 x -> m2 x) -> StreamT m1 a -> StreamT m2 a+hoist' f StreamT {state, step} = StreamT {state, step = f <$> step}+{-# INLINE hoist' #-}++-- * Running streams++-- | Perform one step of a stream, resulting in an updated stream and an output value.+stepStream :: (Functor m) => StreamT m a -> m (Result (StreamT m a) a)+stepStream StreamT {state, step} = mapResultState (`StreamT` step) <$> step state+{-# INLINE stepStream #-}++{- | Run a stream with trivial output.++If the output of a stream does not contain information,+all of its meaning is in its effects.+This function runs the stream indefinitely.+Since it will never return with a value, this function also has no output (its output is void).+The only way it can return is if @m@ includes some effect of termination,+e.g. 'Maybe' or 'Either' could terminate with a 'Nothing' or 'Left' value,+or 'IO' can raise an exception.+-}+reactimate :: (Monad m) => StreamT m () -> m void+reactimate StreamT {state, step} = go state+  where+    go s = do+      Result s' () <- step s+      go s'+{-# INLINE reactimate #-}++-- | Run a stream, collecting the outputs in a lazy, infinite list.+streamToList :: (Monad m) => StreamT m a -> m [a]+streamToList StreamT {state, step} = go state+  where+    go s = do+      Result s' a <- step s+      (a :) <$> go s'+{-# INLINE streamToList #-}++-- * Modifying streams++-- | Change the output type and effect of a stream without changing its state type.+withStreamT :: (Functor m, Functor n) => (forall s. m (Result s a) -> n (Result s b)) -> StreamT m a -> StreamT n b+withStreamT f StreamT {state, step} = StreamT state $ fmap f step+{-# INLINE withStreamT #-}++{- | Buffer the output of a stream, returning one value at a time.++This function lets a stream control the speed at which it produces data,+since it can decide to produce any amount of output at every step.+-}+concatS :: (Monad m) => StreamT m [a] -> StreamT m a+concatS StreamT {state, step} =+  StreamT+    { state = (state, [])+    , step = go+    }+  where+    go (s, []) = do+      Result s' as <- step s+      go (s', as)+    go (s, a : as) = return $ Result (s, as) a+{-# INLINE concatS #-}++-- ** Exception handling++{- | Streams with exceptions are 'Applicative' in the exception type.++Run the first stream until it throws a function as an exception,+  then run the second one. If the second one ever throws an exception,+  apply the function thrown by the first one to it.+-}+applyExcept :: (Monad m) => StreamT (ExceptT (e1 -> e2) m) a -> StreamT (ExceptT e1 m) a -> StreamT (ExceptT e2 m) a+applyExcept (StreamT state1 step1) (StreamT state2 step2) =+  StreamT+    { state = Left state1+    , step+    }+  where+    step (Left s1) = do+      resultOrException <- lift $ runExceptT $ step1 s1+      case resultOrException of+        Right result -> return $! mapResultState Left result+        Left f -> step (Right (state2, f))+    step (Right (s2, f)) = mapResultState (Right . (,f)) <$!> withExceptT f (step2 s2)+{-# INLINE applyExcept #-}++-- | Whenever an exception occurs, output it and retry on the next step.+exceptS :: (Applicative m) => StreamT (ExceptT e m) b -> StreamT m (Either e b)+exceptS StreamT {state, step} =+  StreamT+    { step = \state -> fmap (either (Result state . Left) (fmap Right)) $ runExceptT $ step state+    , state+    }+{-# INLINE exceptS #-}++{- | Run the first stream until it throws an exception.+  If the exception is 'Right', throw it immediately.+  If it is 'Left', run the second stream until it throws a function, which is then applied to the first exception.+-}+selectExcept :: (Monad m) => StreamT (ExceptT (Either e1 e2) m) a -> StreamT (ExceptT (e1 -> e2) m) a -> StreamT (ExceptT e2 m) a+selectExcept (StreamT stateE0 stepE) (StreamT stateF0 stepF) =+  StreamT+    { state = Left stateE0+    , step+    }+  where+    step (Left stateE) = do+      resultOrException <- lift $ runExceptT $ stepE stateE+      case resultOrException of+        Right result -> return $ mapResultState Left result+        Left (Left e1) -> step (Right (e1, stateF0))+        Left (Right e2) -> throwE e2+    step (Right (e1, stateF)) = withExceptT ($ e1) $ mapResultState (Right . (e1,)) <$> stepF stateF++instance (Selective m) => Selective (StreamT m) where+  select (StreamT stateE0 stepE) (StreamT stateF0 stepF) =+    StreamT+      { state = JointState stateE0 stateF0+      , step = \(JointState stateE stateF) ->+          (fmap (mapResultState (`JointState` stateF)) . eitherResult <$> stepE stateE)+            <*? ((\(Result stateF' f) (Result stateE' a) -> Result (JointState stateE' stateF') (f a)) <$> stepF stateF)+      }+    where+      eitherResult :: Result s (Either a b) -> Either (Result s a) (Result s b)+      eitherResult (Result s eab) = bimap (Result s) (Result s) eab++instance (Semialign m) => Semialign (StreamT m) where+  align (StreamT s10 step1) (StreamT s20 step2) =+    StreamT+      { state = These s10 s20+      , step = \case+          This s1 -> mapResultState This . fmap This <$> step1 s1+          That s2 -> mapResultState That . fmap That <$> step2 s2+          These s1 s2 -> commuteTheseResult <$> align (step1 s1) (step2 s2)+      }+    where+      commuteTheseResult :: These (Result s1 a1) (Result s2 a2) -> Result (These s1 s2) (These a1 a2)+      commuteTheseResult (This (Result s1 a1)) = Result (This s1) (This a1)+      commuteTheseResult (That (Result s2 a2)) = Result (That s2) (That a2)+      commuteTheseResult (These (Result s1 a1) (Result s2 a2)) = Result (These s1 s2) (These a1 a2)+  {-# INLINE align #-}++instance (Align m) => Align (StreamT m) where+  nil = constM nil+  {-# INLINE nil #-}++-- ** Fix points, or recursive definitions++{- | Recursively define a stream from a recursive definition of the state, and of the step function.++If you want to define a stream recursively, this is not possible directly.+For example, consider this definition:+@+loops :: Monad m => StreamT m [Int]+loops = (:) <$> unfold_ 0 (+ 1) <*> loops+@+The defined value @loops@ contains itself in its definition.+This means that the internal state type of @loops@ must itself be recursively defined.+But GHC cannot do this automatically, because type level and value level are separate.+Instead, we need to spell out the type level recursion explicitly with a type constructor,+over which we will take the fixpoint.++In this example, we can figure out from the definitions that:+1. @'unfold_' 0 (+ 1)@ has @0 :: Int@ as state+2. '(:)' does not change the state+3. '<*>' takes the product of both states++So the internal state @s@ of @loops@ must satisfy the equation @s = (Int, s)@.+If the recursion is written as above, it tries to compute the infinite tuple @(Int, (Int, (Int, ...)))@, which hangs.+Instead, we need to define a type operator over which we take the fixpoint:++@+-- You need to write this:+data Loops x = Loops Int x++-- The library supplies:+data Fix f = Fix f (Fix f)+type LoopsState = Fix Loops+@++We can then use 'fixStream' to define the recursive definition of @loops@.+For this, we have to to tediously inline the definitions of 'unfold_', '(:)', and '<*>',+until we arrive at an explicit recursive definition of the state and the step function of @loops@, separately.+These are the two arguments of 'fixStream'.++@+loops :: Monad m => StreamT m [Int]+loops = fixStream (Loops 0) $ \fixStep (Loops n fixState) -> do+  Result s' a <- fixStep fixState+  return $ Result (Loops (n + 1) s') a+@+-}+fixStream ::+  (Functor m) =>+  -- | The recursive definition of the state of the stream.+  (forall s. s -> t s) ->+  -- | The recursive definition of the step function of the stream.+  ( forall s.+    (s -> m (Result s a)) ->+    (t s -> m (Result (t s) a))+  ) ->+  StreamT m a+fixStream transformState transformStep =+  StreamT+    { state = fixState transformState+    , step+    }+  where+    step Fix {getFix} = mapResultState Fix <$> transformStep step getFix++-- | A generalisation of 'fixStream' where the step definition is allowed to depend on the state.+fixStream' ::+  (Functor m) =>+  (forall s. s -> t s) ->+  -- | The recursive definition of the state of the stream.+  (forall s. s -> (s -> m (Result s a)) -> (t s -> m (Result (t s) a))) ->+  -- | The recursive definition of the step function of the stream.+  StreamT m a+fixStream' transformState transformStep =+  StreamT+    { state = fixState transformState+    , step+    }+  where+    step fix@(Fix {getFix}) = mapResultState Fix <$> transformStep fix step getFix++{- | The solution to the equation @'fixA stream = stream <*> 'fixA' stream@.++Such a fix point operator needs to be used instead of the above direct definition because recursive definitions of streams+loop at runtime due to the initial encoding of the state.+-}+fixA :: (Applicative m) => StreamT m (a -> a) -> StreamT m a+fixA StreamT {state, step} = fixStream (JointState state) $+  \stepA (JointState s ss) -> apResult <$> step s <*> stepA ss
+ src/Data/Stream/Except.hs view
@@ -0,0 +1,70 @@+module Data.Stream.Except where++-- base+import Control.Monad (ap)+import Data.Void++-- transformers+import Control.Monad.Trans.Class+import Control.Monad.Trans.Except++-- mmorph+import Control.Monad.Morph (MFunctor, hoist)++-- selective+import Control.Selective++-- automaton+import Data.Stream.Final (Final (..))+import Data.Stream.Final.Except+import Data.Stream.Optimized (OptimizedStreamT, applyExcept, constM, selectExcept)+import Data.Stream.Optimized qualified as StreamOptimized++{- | A stream that can terminate with an exception.++In @automaton@, such streams mainly serve as a vehicle to bring control flow to 'Data.Automaton.Trans.Except.AutomatonExcept'+(which is based on 'StreamExcept'), and the docs there apply here as well.++'StreamExcept' is not only a 'Monad', it also has more efficient 'Selective', 'Applicative', and 'Functor' interfaces.+-}+data StreamExcept a m e+  = -- | When using '>>=', this encoding will be used.+    FinalExcept (Final (ExceptT e m) a)+  | -- | This is usually the faster encoding, as it can be optimized by GHC.+    InitialExcept (OptimizedStreamT (ExceptT e m) a)++toFinal :: (Functor m) => StreamExcept a m e -> Final (ExceptT e m) a+toFinal (FinalExcept final) = final+toFinal (InitialExcept initial) = StreamOptimized.toFinal initial++runStreamExcept :: StreamExcept a m e -> OptimizedStreamT (ExceptT e m) a+runStreamExcept (FinalExcept final) = StreamOptimized.fromFinal final+runStreamExcept (InitialExcept initial) = initial++instance (Monad m) => Functor (StreamExcept a m) where+  fmap f (FinalExcept fe) = FinalExcept $ hoist (withExceptT f) fe+  fmap f (InitialExcept ae) = InitialExcept $ hoist (withExceptT f) ae++instance (Monad m) => Applicative (StreamExcept a m) where+  pure = InitialExcept . constM . throwE+  InitialExcept f <*> InitialExcept a = InitialExcept $ applyExcept f a+  f <*> a = ap f a++instance (Monad m) => Selective (StreamExcept a m) where+  select (InitialExcept e) (InitialExcept f) = InitialExcept $ selectExcept e f+  select e f = selectM e f++-- | 'return'/'pure' throw exceptions, '(>>=)' uses the last thrown exception as input for an exception handler.+instance (Monad m) => Monad (StreamExcept a m) where+  (>>) = (*>)+  ae >>= f = FinalExcept $ handleExceptT (toFinal ae) (toFinal . f)++instance MonadTrans (StreamExcept a) where+  lift = InitialExcept . constM . ExceptT . fmap Left++instance MFunctor (StreamExcept a) where+  hoist morph (InitialExcept automaton) = InitialExcept $ hoist (mapExceptT morph) automaton+  hoist morph (FinalExcept final) = FinalExcept $ hoist (mapExceptT morph) final++safely :: (Monad m) => StreamExcept a m Void -> OptimizedStreamT m a+safely = hoist (fmap (either absurd id) . runExceptT) . runStreamExcept
+ src/Data/Stream/Final.hs view
@@ -0,0 +1,63 @@+module Data.Stream.Final where++-- base+import Control.Applicative (Alternative (..))++-- mmorph+import Control.Monad.Morph (MFunctor (..))++-- automaton+import Data.Stream (StreamT (..), stepStream)+import Data.Stream.Result++{- | A stream transformer in final encoding.++One step of the stream transformer performs a monadic action and results in an output and a new stream.+-}+newtype Final m a = Final {getFinal :: m (Result (Final m a) a)}++{- | Translate an initially encoded stream into a finally encoded one.++This is usually a performance penalty.+-}+toFinal :: (Functor m) => StreamT m a -> Final m a+toFinal automaton = Final $ mapResultState toFinal <$> stepStream automaton+{-# INLINE toFinal #-}++{- | Translate a finally encoded stream into an initially encoded one.++The internal state is the stream itself.+-}+fromFinal :: Final m a -> StreamT m a+fromFinal final =+  StreamT+    { state = final+    , step = getFinal+    }+{-# INLINE fromFinal #-}++instance MFunctor Final where+  hoist morph = go+    where+      go Final {getFinal} = Final $ morph $ mapResultState go <$> getFinal++instance (Functor m) => Functor (Final m) where+  fmap f Final {getFinal} = Final $ fmap f . mapResultState (fmap f) <$> getFinal++instance (Applicative m) => Applicative (Final m) where+  pure a = go+    where+      go = Final $! pure $! Result go a++  Final mf <*> Final ma = Final $! (\(Result cf f) (Result ca a) -> Result (cf <*> ca) $! f a) <$> mf <*> ma++-- | Constantly perform the same effect, without remembering a state.+constM :: (Functor m) => m a -> Final m a+constM ma = go+  where+    go = Final $ Result go <$> ma++instance (Alternative m) => Alternative (Final m) where+  empty = constM empty++  Final ma1 <|> Final ma2 = Final $ ma1 <|> ma2
+ src/Data/Stream/Final/Except.hs view
@@ -0,0 +1,18 @@+module Data.Stream.Final.Except where++-- transformers+import Control.Monad.Trans.Class+import Control.Monad.Trans.Except (ExceptT, runExceptT)++-- automaton+import Data.Stream.Final (Final (..))+import Data.Stream.Result (mapResultState)++handleExceptT :: (Monad m) => Final (ExceptT e1 m) b -> (e1 -> Final (ExceptT e2 m) b) -> Final (ExceptT e2 m) b+handleExceptT final handler = go final+  where+    go final = Final $ do+      resultOrException <- lift $ runExceptT $ getFinal final+      case resultOrException of+        Right result -> return $! mapResultState go result+        Left e -> getFinal $ handler e
+ src/Data/Stream/Internal.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StrictData #-}++-- | Helper functions and types for Data.Stream. You will typically not need them.+module Data.Stream.Internal where++-- | A strict tuple type+data JointState a b = JointState a b++-- | Internal state of the result of 'Alternative' constructions+data Alternatively stateL stateR = Undecided | DecideL stateL | DecideR stateR++-- | Internal state of 'many' and 'some'+data Many state x = NotStarted | Ongoing x state | Finished++-- newtype makes GHC loop on using fixStream+{- HLINT ignore Fix "Use newtype instead of data" -}+data Fix t = Fix {getFix :: ~(t (Fix t))}++fixState :: (forall s. s -> t s) -> Fix t+fixState transformState = go+  where+    go = Fix $ transformState go
+ src/Data/Stream/Optimized.hs view
@@ -0,0 +1,221 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE UndecidableInstances #-}++{- | An optimization layer on "Data.Stream".++Since both variants are semantically the same, not the full API of "Data.Stream" is replicated here.+-}+module Data.Stream.Optimized where++-- base+import Control.Applicative (Alternative (..), Applicative (..), liftA2)+import Data.Monoid (Ap (..))+import Prelude hiding (Applicative (..))++-- transformers+import Control.Monad.Trans.Except (ExceptT)++-- selective+import Control.Selective (Selective (select))++-- simple-affine-space+import Data.VectorSpace++-- mmorph+import Control.Monad.Morph++-- automaton++import Data.Align (Align, Semialign)+import Data.Semialign (Align (..), Semialign (..))+import Data.Stream hiding (hoist')+import Data.Stream qualified as StreamT+import Data.Stream.Final (Final (..))+import Data.Stream.Final qualified as Final (fromFinal, toFinal)+import Data.Stream.Result++{- | An optimized version of 'StreamT' which has an extra constructor for stateless streams.++In most cases, using 'OptimizedStreamT' is preferable over 'StreamT',+because building up bigger programs with 'StreamT' will build up big accumulations of trivial states.+The API of 'OptimizedStreamT' only keeps the nontrivial parts of the state.++Semantically, both types are the same.+-}+data OptimizedStreamT m a+  = -- | Embed a 'StreamT'. Take care only to use this constructor on streams with nontrivial state.+    Stateful (StreamT m a)+  | -- | A stateless stream is simply an action in a monad which is performed repetitively.+    Stateless (m a)+  deriving (Functor)++{- | Remove the optimization layer.++For stateful streams, this is just the identity.+A stateless stream is encoded as a stream with state '()'.+-}+toStreamT :: (Functor m) => OptimizedStreamT m b -> StreamT m b+toStreamT (Stateful stream) = stream+toStreamT (Stateless m) = StreamT {state = (), step = const $ Result () <$> m}+{-# INLINE toStreamT #-}++-- | Only builds up tuples of states if both streams are stateful.+instance (Applicative m) => Applicative (OptimizedStreamT m) where+  pure = Stateless . pure+  {-# INLINE pure #-}++  Stateful stream1 <*> Stateful stream2 = Stateful $ stream1 <*> stream2+  Stateless m <*> Stateful (StreamT state0 step) = Stateful $ StreamT state0 $ \state -> fmap . ($) <$> m <*> step state+  Stateful (StreamT state0 step) <*> Stateless m = Stateful $ StreamT state0 $ \state -> flip (fmap . flip ($)) <$> step state <*> m+  Stateless mf <*> Stateless ma = Stateless $ mf <*> ma+  {-# INLINE (<*>) #-}++deriving via Ap (OptimizedStreamT m) a instance (Applicative m, Num a) => Num (OptimizedStreamT m a)++instance (Applicative m, Fractional a) => Fractional (OptimizedStreamT m a) where+  fromRational = pure . fromRational+  recip = fmap recip++instance (Applicative m, Floating a) => Floating (OptimizedStreamT m a) where+  pi = pure pi+  exp = fmap exp+  log = fmap log+  sin = fmap sin+  cos = fmap cos+  asin = fmap asin+  acos = fmap acos+  atan = fmap atan+  sinh = fmap sinh+  cosh = fmap cosh+  asinh = fmap asinh+  acosh = fmap acosh+  atanh = fmap atanh++instance (VectorSpace v s, Eq s, Floating s, Applicative m) => VectorSpace (OptimizedStreamT m v) (OptimizedStreamT m s) where+  zeroVector = pure zeroVector+  (*^) = liftA2 (*^)+  (^+^) = liftA2 (^+^)+  dot = liftA2 dot+  normalize = fmap normalize++instance (Alternative m) => Alternative (OptimizedStreamT m) where+  empty = Stateless empty+  {-# INLINE empty #-}++  -- The semantics prescribe that we save the state which stream was selected.+  stream1 <|> stream2 = Stateful $ toStreamT stream1 <|> toStreamT stream2+  {-# INLINE (<|>) #-}++  many stream = Stateful $ many $ toStreamT stream+  {-# INLINE many #-}++  some stream = Stateful $ some $ toStreamT stream+  {-# INLINE some #-}++instance (Selective m) => Selective (OptimizedStreamT m) where+  select (Stateless mab) (Stateless f) = Stateless $ select mab f+  select stream1 stream2 = Stateful $ select (toStreamT stream1) (toStreamT stream2)++instance (Semialign m) => Semialign (OptimizedStreamT m) where+  align (Stateless ma) (Stateless mb) = Stateless $ align ma mb+  align stream1 stream2 = Stateful $ align (toStreamT stream1) (toStreamT stream2)++instance (Align m) => Align (OptimizedStreamT m) where+  nil = Stateless nil++instance MFunctor OptimizedStreamT where+  hoist = hoist'+  {-# INLINE hoist #-}++-- | Like 'hoist', but without the @'Monad' m2@ constraint.+hoist' :: (forall x. m1 x -> m2 x) -> OptimizedStreamT m1 a -> OptimizedStreamT m2 a+hoist' f (Stateful stream) = Stateful $ StreamT.hoist' f stream+hoist' f (Stateless m) = Stateless $ f m+{-# INLINE hoist' #-}++-- | Change the output type and effect of a stream without changing its state type.+mapOptimizedStreamT :: (Functor m, Functor n) => (forall s. m (Result s a) -> n (Result s b)) -> OptimizedStreamT m a -> OptimizedStreamT n b+mapOptimizedStreamT f (Stateful stream) = Stateful $ withStreamT f stream+mapOptimizedStreamT f (Stateless m) = Stateless $ fmap output $ f $ fmap (Result ()) m+{-# INLINE mapOptimizedStreamT #-}++{- | Map a monad-independent morphism of streams to optimized streams.++In contrast to 'handleOptimized', the stream morphism must be independent of the monad.+-}+withOptimized :: (Monad n) => (forall m. (Monad m) => StreamT m a -> StreamT m b) -> OptimizedStreamT n a -> OptimizedStreamT n b+withOptimized f stream = Stateful $ f $ toStreamT stream++{- | Map a morphism of streams to optimized streams.++In contrast to 'withOptimized', the monad type is allowed to change.+-}+handleOptimized :: (Functor m) => (StreamT m a -> StreamT n b) -> OptimizedStreamT m a -> OptimizedStreamT n b+handleOptimized f stream = Stateful $ f $ toStreamT stream++{- | Run a stream with trivial output.++See 'Data.Stream.reactimate'.+-}+reactimate :: (Monad m) => OptimizedStreamT m () -> m void+reactimate (Stateful stream) = StreamT.reactimate stream+reactimate (Stateless f) = go+  where+    go = f *> go+{-# INLINE reactimate #-}++{- | A stateless stream.++This function is typically preferable over 'Data.Stream.constM',+since the optimized version doesn't create a state type.+-}+constM :: m a -> OptimizedStreamT m a+constM = Stateless+{-# INLINE constM #-}++-- | Perform one step of a stream, resulting in an updated stream and an output value.+stepOptimizedStream :: (Functor m) => OptimizedStreamT m a -> m (Result (OptimizedStreamT m a) a)+stepOptimizedStream (Stateful stream) = mapResultState Stateful <$> stepStream stream+stepOptimizedStream oa@(Stateless m) = Result oa <$> m+{-# INLINE stepOptimizedStream #-}++{- | Translate to the final encoding of streams.++This will typically be a performance penalty.+-}+toFinal :: (Functor m) => OptimizedStreamT m a -> Final m a+toFinal (Stateful stream) = Final.toFinal stream+toFinal (Stateless f) = go+  where+    go = Final $ Result go <$> f+{-# INLINE toFinal #-}++{- | Translate a stream from final encoding to stateful, initial encoding.+  The internal state is the stream itself.+-}+fromFinal :: Final m a -> OptimizedStreamT m a+fromFinal = Stateful . Final.fromFinal+{-# INLINE fromFinal #-}++-- | See 'Data.Stream.concatS'.+concatS :: (Monad m) => OptimizedStreamT m [a] -> OptimizedStreamT m a+concatS stream = Stateful $ StreamT.concatS $ toStreamT stream+{-# INLINE concatS #-}++-- | See 'Data.Stream.exceptS'.+exceptS :: (Monad m) => OptimizedStreamT (ExceptT e m) b -> OptimizedStreamT m (Either e b)+exceptS stream = Stateful $ StreamT.exceptS $ toStreamT stream+{-# INLINE exceptS #-}++-- | See 'Data.Stream.applyExcept'.+applyExcept :: (Monad m) => OptimizedStreamT (ExceptT (e1 -> e2) m) a -> OptimizedStreamT (ExceptT e1 m) a -> OptimizedStreamT (ExceptT e2 m) a+applyExcept streamF streamA = Stateful $ StreamT.applyExcept (toStreamT streamF) (toStreamT streamA)+{-# INLINE applyExcept #-}++-- | See 'Data.Stream.selectExcept'.+selectExcept :: (Monad m) => OptimizedStreamT (ExceptT (Either e1 e2) m) a -> OptimizedStreamT (ExceptT (e1 -> e2) m) a -> OptimizedStreamT (ExceptT e2 m) a+selectExcept streamE streamF = Stateful $ StreamT.selectExcept (toStreamT streamE) (toStreamT streamF)+{-# INLINE selectExcept #-}
+ src/Data/Stream/Result.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE StrictData #-}++module Data.Stream.Result where++-- base+import Data.Bifunctor (Bifunctor (..))++-- automaton+import Data.Stream.Internal++{- | A tuple that is strict in its first argument.++This type is used in streams and automata to encode the result of a state transition.+The new state should always be strict to avoid space leaks.+-}+data Result s a = Result {resultState :: s, output :: ~a}+  deriving (Functor)++instance Bifunctor Result where+  second = fmap+  first = mapResultState++-- | Apply a function to the state of a 'Result'.+mapResultState :: (s1 -> s2) -> Result s1 a -> Result s2 a+mapResultState f Result {resultState, output} = Result {resultState = f resultState, output}+{-# INLINE mapResultState #-}++-- | Analogous to 'Applicative''s '(<*>)'.+apResult :: Result s1 (a -> b) -> Result s2 a -> Result (JointState s1 s2) b+apResult (Result resultStateA outputF) (Result resultStateB outputA) = Result (JointState resultStateA resultStateB) $ outputF outputA+{-# INLINE apResult #-}++-- | A state transformer with 'Result' instead of a standard tuple as its result.+newtype ResultStateT s m a = ResultStateT {getResultStateT :: s -> m (Result s a)}+  deriving (Functor)++instance (Monad m) => Applicative (ResultStateT s m) where+  pure output = ResultStateT (\resultState -> pure Result {resultState, output})++  ResultStateT mf <*> ResultStateT ma = ResultStateT $ \s -> do+    Result s' f <- mf s+    Result s'' a <- ma s'+    pure (Result s'' (f a))
+ test/Automaton.hs view
@@ -0,0 +1,93 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Automaton where++-- base+import Control.Applicative (Alternative (..))+import Control.Arrow+import Control.Monad (guard)+import Data.Functor.Identity (runIdentity)+import Data.List (uncons)+import Data.Maybe (maybeToList)++-- transformers+import Control.Monad.State.Strict (StateT (..))++-- selective+import Control.Selective ((<*?))++-- tasty+import Test.Tasty (testGroup)++-- tasty-quickcheck+import Test.Tasty.QuickCheck++-- tasty-hunit+import Test.Tasty.HUnit (testCase, (@?=))++-- automaton+import Automaton.Except+import Data.Automaton+import Data.Automaton.Final+import Data.Automaton.Trans.Maybe++tests =+  testGroup+    "Automaton"+    [ testGroup+        "Alternative"+        [ testGroup+            "<|>"+            [ testProperty "has same semantics as final" $+                \(input :: [(Maybe Int, Maybe Int)]) ->+                  embed ((arr fst >>> inMaybe) <|> (arr snd >>> inMaybe)) input+                    === embed (fromFinal $ (arr fst >>> toFinal inMaybe) <|> (arr snd >>> toFinal inMaybe)) input+            ]+        , testGroup+            "some"+            [ testCase "Maybe" $ embed (some $ arrM id) [Nothing] @?= (Nothing :: Maybe [[()]])+            , testCase "Parser" $ runParser (embed (some $ constM aChar) [(), ()]) "hi" @?= [(["h", "i"], "")]+            ]+        , testGroup+            "many"+            [ testCase "Maybe" $ embed (many $ arrM id) [Nothing] @?= (Just [[]] :: Maybe [[()]])+            , testCase "Parser" $ runParser (many (char 'h')) "hi" @?= [("h", "i"), ("", "hi")]+            ]+        ]+    , testGroup+        "parallely"+        [ testCase "Outputs separate sums" $ runIdentity (embed (parallely sumN) [[], [], [1, 2], [10, 20], [100], [], [1000, 200]]) @?= [[], [], [1, 2], [11, 22], [111], [], [1111, 222]]+        ]+    , testGroup+        "Selective"+        [ testCase "selects second Automaton conditionally" $+            runIdentity (embed (right sumN <*? arr (const (* 2))) [Right 1, Right 2, Left 10, Right 3, Left 20]) @?= [1, 3, 20, 6, 40]+        ]+    , testCase "count" $ runIdentity (embed count [(), (), ()]) @?= [1, 2, 3]+    , testCase "delay" $ runIdentity (embed (count >>> delay 0) [(), (), ()]) @?= [0, 1, 2]+    , testCase "sumS" $ runIdentity (embed (arr (const (1 :: Float)) >>> sumS) [(), (), ()]) @?= [1, 2, 3]+    , testCase "sumN" $ runIdentity (embed (arr (const (1 :: Integer)) >>> sumN) [(), (), ()]) @?= [1, 2, 3]+    , testCase "lastS" $ runIdentity (embed (lastS 0) [Nothing, Just 10]) @?= [0, 10]+    , Automaton.Except.tests+    ]++inMaybe :: Automaton Maybe (Maybe a) a+inMaybe = hoistS (runIdentity . runMaybeT) inMaybeT++-- * Parser helper type to test many & some++newtype Parser a = Parser {getParser :: StateT String [] a}+  deriving (Functor, Applicative, Monad, Alternative)++runParser :: Parser a -> String -> [(a, String)]+runParser = runStateT . getParser++aChar :: Parser Char+aChar = Parser $ StateT $ maybeToList . uncons++char :: Char -> Parser Char+char c = do+  c' <- aChar+  guard $ c == c'+  return c
+ test/Automaton/Except.hs view
@@ -0,0 +1,16 @@+module Automaton.Except where++-- base+import Control.Monad.Identity (Identity (runIdentity))++-- tasty+import Test.Tasty (testGroup)++-- tasty-hunit+import Test.Tasty.HUnit (testCase, (@?=))++-- rhine+import Data.Automaton (embed)+import Data.Automaton.Trans.Except (safe, safely, step)++tests = testGroup "Except" [testCase "step" $ runIdentity (embed (safely $ step (\a -> return (a, ())) >> safe 0) [1, 1, 1]) @?= [1, 0, 0]]
+ test/Main.hs view
@@ -0,0 +1,16 @@+module Main where++-- tasty+import Test.Tasty++-- automaton+import Automaton+import Stream++main =+  defaultMain $+    testGroup+      "Main"+      [ Automaton.tests+      , Stream.tests+      ]
+ test/Stream.hs view
@@ -0,0 +1,31 @@+module Stream where++-- base+import Control.Monad.Identity (Identity (..))++-- selective+import Control.Selective++-- tasty+import Test.Tasty (testGroup)++-- tasty-hunit+import Test.Tasty.HUnit (testCase, (@?=))++-- automaton+import Automaton+import Data.Stream (streamToList, unfold)+import Data.Stream.Result++tests =+  testGroup+    "Stream"+    [ Automaton.tests+    , testGroup+        "Selective"+        [ testCase "Selects second stream based on first stream" $+            let automaton1 = unfold 0 (\n -> Result (n + 1) (if even n then Right n else Left n))+                automaton2 = pure (* 10)+             in take 10 (runIdentity (streamToList (automaton1 <*? automaton2))) @?= [0, 10, 2, 30, 4, 50, 6, 70, 8, 90]+        ]+    ]