packages feed

automaton-1.6: src/Data/Stream/Optimized.hs

{-# LANGUAGE DeriveTraversable #-}
{-# 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

-- align
import Data.Align (Align, Semialign)
import Data.Semialign (Align (..), Semialign (..))

-- automaton
import Data.Stream hiding (hoist')
import Data.Stream qualified as StreamT
import Data.Stream.Recursive (Recursive (..))
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, Foldable, Traversable)

{- | 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

-- | Like 'withOptimized', but with fewer constraints.
withOptimizedF :: (Functor n) => (forall m. (Functor m) => StreamT m a -> StreamT m b) -> OptimizedStreamT n a -> OptimizedStreamT n b
withOptimizedF 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 recursive encoding of streams.

This will typically be a performance penalty.
-}
toRecursive :: (Functor m) => OptimizedStreamT m a -> Recursive m a
toRecursive (Stateful stream) = StreamT.toRecursive stream
toRecursive (Stateless f) = go
  where
    go = Recursive $ Result go <$> f
{-# INLINE toRecursive #-}

{- | Translate a stream from recursive encoding to stateful, coalgebraic encoding.
  The internal state is the stream itself.
-}
fromRecursive :: Recursive m a -> OptimizedStreamT m a
fromRecursive = Stateful . StreamT.fromRecursive
{-# INLINE fromRecursive #-}

-- | 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 #-}