boombox-0.0: src/Data/Boombox/Tape.hs
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
{-# LANGUAGE Rank2Types, ScopedTypeVariables #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts, FlexibleInstances, UndecidableInstances #-}
module Data.Boombox.Tape (Tape(..)
-- * Consuming tapes
, headTape
, cueTape
-- * Constructing tapes
, yield
, yieldMany
, effect
, repeater
-- * Transforming tapes
, flattenTape
, filterTape
, foldTape
, hoistTransTape
, hoistTape
, transTape
, controlTape
, pushBack
, intercept
-- * Time series
, Chronological(..)
, EventOrder(..)
) where
import Control.Category
import Control.Applicative
import Data.Functor.Apply
import Data.Functor.Identity
import Prelude hiding (id, (.))
import Control.Comonad.Env
import Control.Comonad.Store
import Control.Comonad.Traced hiding ((<>))
import Data.Semigroup
import Control.Arrow
-- | @Tape w m a@ is a producer of values with a type @a@.
-- It may cause effects @m@ and has a comonadic control @w@.
newtype Tape w m a = Tape { unconsTape :: m (a, w (Tape w m a)) }
deriving (Functor)
yield :: Applicative m => a -> w (Tape w m a) -> Tape w m a
yield a w = Tape $ pure (a, w)
{-# INLINE yield #-}
effect :: Monad m => m (Tape w m a) -> Tape w m a
effect m = Tape $ m >>= unconsTape
{-# INLINE effect #-}
-- | Build a tape that yields the same value, with the very same effect and exactly the same control.
repeater :: (Functor m, Comonad w) => m (w a) -> Tape w m a
repeater m = Tape $ fmap (\w -> (extract w, repeater m <$ w)) m
-- | Take the first element of the tape.
headTape :: Functor m => Tape w m a -> m a
headTape = fmap fst . unconsTape
-- | Denudate the control without dropping a value.
cueTape :: (Comonad w, Applicative m) => Tape w m a -> m (w (Tape w m a))
cueTape = fmap (\(a, w) -> extend (yield a) w) . unconsTape
-- | Flatten a tape of 'Foldable' containers.
flattenTape :: (Comonad w, Foldable f, Monad m) => Tape w m (f a) -> Tape w m a
flattenTape = foldTape id
{-# INLINE flattenTape #-}
foldTape :: (Comonad w, Foldable f, Monad m) => (a -> f b) -> Tape w m a -> Tape w m b
foldTape f = go where
go t = Tape $ unconsTape t >>= \(a, w) -> unconsTape $ yieldMany (f a) (fmap go w)
{-# INLINE foldTape #-}
filterTape :: (Comonad w, Monad m) => (a -> Bool) -> Tape w m a -> Tape w m a
filterTape p = go where
go t = Tape $ unconsTape t >>= \(a, w) -> if p a then return (a, fmap go w) else unconsTape (go (extract w))
yieldMany :: (Comonad w, Foldable f, Applicative m) => f a -> w (Tape w m a) -> Tape w m a
yieldMany f w = extract $ foldr (extend . yield) w f
{-# INLINE yieldMany #-}
-- | Apply a monadic function to a tape.
intercept :: (Functor w, Monad m) => (a -> m b) -> Tape w m a -> Tape w m b
intercept k t = Tape $ unconsTape t >>= \(a, w) -> (\b -> (b, fmap (intercept k) w)) <$> k a
hoistTransTape :: (Functor w, Functor n) => (forall x. v x -> w x) -> (forall x. m x -> n x) -> Tape v m a -> Tape w n a
hoistTransTape s t = go where
go (Tape m) = Tape $ fmap (\(a, w) -> (a, fmap go (s w))) (t m)
{-# INLINE hoistTransTape #-}
-- | Apply natural transformation to the comonadic control surface.
hoistTape :: (Functor w, Functor m) => (forall x. v x -> w x) -> Tape v m a -> Tape w m a
hoistTape t = hoistTransTape t id
{-# INLINE hoistTape #-}
-- | Transform effects produced by the tape.
transTape :: (Functor w, Functor n) => (forall x. m x -> n x) -> Tape w m a -> Tape w n a
transTape = hoistTransTape id
{-# INLINE transTape #-}
-- | Operate on the control surface just once.
controlTape :: Functor m => (w (Tape w m a) -> w (Tape w m a)) -> Tape w m a -> Tape w m a
controlTape t (Tape m) = Tape $ fmap (second t) m
-- | Push some values back to a tape.
pushBack :: (Foldable f, Comonad w, Monad m) => f a -> Tape w m a -> Tape w m a
pushBack f t = effect $ yieldMany f <$> cueTape t
-- | 'Chronological' functor is like 'Apply', but the operation may fail due to a time lag.
class Functor f => Chronological f where
coincidence :: f a -> f b -> EventOrder (f (a, b))
data EventOrder a = Simultaneous a
| LeftFirst
| RightFirst
deriving Functor
instance Chronological Identity where
coincidence (Identity a) (Identity b) = Simultaneous (Identity (a, b))
instance Chronological ((->) i) where
coincidence f g = Simultaneous $ liftA2 (,) f g
instance Ord i => Chronological ((,) i) where
coincidence (i, a) (j, b) = case compare i j of
EQ -> Simultaneous (i, (a, b))
LT -> LeftFirst
GT -> RightFirst
instance (Ord i, Chronological w) => Chronological (EnvT i w) where
coincidence (EnvT i v) (EnvT j w) = case compare i j of
EQ -> EnvT i <$> coincidence v w
LT -> LeftFirst
GT -> RightFirst
instance (Ord i, Chronological w) => Chronological (StoreT i w) where
coincidence (StoreT v i) (StoreT w j) = case compare i j of
EQ -> (\wfg -> StoreT (fmap (uncurry $ liftA2 (,)) wfg) i) <$> coincidence v w
LT -> LeftFirst
GT -> RightFirst
instance Chronological w => Chronological (TracedT m w) where
coincidence (TracedT v) (TracedT w) = fmap (TracedT . fmap (uncurry $ liftA2 (,))) $ coincidence v w
instance (Chronological w, Monad m, Semigroup a)
=> Semigroup (Tape w m a) where
s <> t = Tape $ do
(a, v) <- unconsTape s
(b, w) <- unconsTape t
case coincidence v w of
Simultaneous u -> return (a <> b, fmap (uncurry (<>)) u)
LeftFirst -> return (a, fmap (<> t) v)
RightFirst -> return (b, fmap (s <>) w)