packages feed

bot-0.3: src/Data/Bot/Edit.hs

{-# OPTIONS -Wall #-}
{-# LANGUAGE GADTs, KindSignatures, TypeOperators, GeneralizedNewtypeDeriving
  , ScopedTypeVariables, Arrows #-}
----------------------------------------------------------------------
-- |
-- Module      :  Data.Bot.Edit
-- Copyright   :  (c) Conal Elliott 2008
-- License     :  BSD3
-- 
-- Maintainer  :  conal@conal.net
-- Stability   :  experimental
-- 
-- Edit-bots
----------------------------------------------------------------------

module Data.Bot.Edit where

import Prelude hiding (id,(.))
import Control.Category
import Control.Arrow

import Data.Maybe (fromMaybe)

-- import Control.Arrow.Transformer
import Control.Arrow.Transformer.Automaton

import Data.Stream hiding ()
import qualified Data.Stream as S

-- | An edit/change to a value.  Represents @a -> a@ but as data, to allow
-- analysis.
data Edit :: * -> * where
  Konst   :: a -> Edit a                     -- const
  Idy     :: Edit a                          -- id
  (:***)  :: Edit a -> Edit b -> Edit (a,b)  -- ***
  (:>>>)  :: Edit a -> Edit a -> Edit a      -- (>>>)

-- Note: why use *** instead of First?  Because the smart constructor
-- rewrite rules seem to come out more simply.

-- | Interpret an edit as an arrow value (e.g., function).
edit :: Arrow (~>) => Edit a -> (a ~> a)
edit (Konst a)      = arr (const a)
edit Idy            = arr id
edit (eda :*** edb) = edit eda *** edit edb
edit (ed :>>> ed')  = edit ed  >>> edit ed'

-- TODO: import Data.Pair.  Replace unzip* with unpair and zip* with
-- pair.  Would add a TypeCompose dependency.

-- | Zip up two edits into a pair edit
zipE :: Edit a -> Edit b -> Edit (a,b)
zipE = (***:)

-- Try splitting one edit @ed@ into two independent edits @eda@ and @edb@
-- such that @ed == eda *** edb@.
unzipE :: Edit (a,b) -> Maybe (Edit a, Edit b)

unzipE (Konst (a,b))  = Just (Konst a, Konst b)
unzipE Idy            = Just (Idy, Idy)
unzipE (eda :*** edb) = Just (eda, edb)
unzipE (_   :>>> _  ) = Nothing

-- | Smart constructor for '(:***)'.  Applies some rewrites.
(***:) :: Edit a -> Edit b -> Edit (a,b)

Idy     ***: Idy     = Idy
Konst a ***: Konst b = Konst (a,b)
ed      ***: ed'     = ed :*** ed'

-- | Smart constructor for '(:>>>)'.  Applies some rewrites.
(>>>:) :: Edit a -> Edit a -> Edit a

Idy        >>>: f            = f
f          >>>: Idy          = f
_          >>>: k@(Konst _)  = k
(f :*** g) >>>: (f' :*** g') = (f >>>: f') ***: (g >>>: g')
ed         >>>: ed'          = ed :>>> ed'


-- | A tweak is an edit and a new value.  This new value can be computed
-- from the edit and a previous value.  It's stored here (lazily) so it
-- can be shared.
data Tweak a = Tweak (Edit a) a

-- | Simple 'Konst' tweaker.  A catch-all, but not helpful for performance.
tweak :: a -> Tweak a
tweak a = Tweak (Konst a) a

-- Zip up two tweaks into a pair tweak.
zipT :: Tweak a -> Tweak b -> Tweak (a,b)
zipT (Tweak ea a) (Tweak eb b) = Tweak (zipE ea eb) (a,b)

-- | Split a pair tweak in two.
unzipT :: Tweak (a,b) -> (Tweak a, Tweak b)
unzipT (Tweak ed (a,b)) = (Tweak eda a, Tweak edb b)
 where
   (eda,edb) = fromMaybe (Konst a, Konst b) (unzipE ed)

-- | Stream of tweaks
type Tweaks a = Stream (Tweak a)

-- Zip up two tweak streams into a pair-valued tweak stream.
zipTs :: Tweaks a -> Tweaks c -> Tweaks (a,c)
zipTs = (fmap.fmap.fmap) (uncurry zipT) S.zip

-- zipTs tas tcs = fmap (uncurry zipT) $ S.zip tas tcs

-- | Split a pair-valued tweak stream in two.
unzipTs :: Tweaks (a,c) -> (Tweaks a, Tweaks c)
unzipTs = S.unzip . fmap unzipT


-- | Reactive (discretely varying) value.  An initial value and a stream
-- of tweaks.  
-- 
-- Although 'Reactive' is a 'Functor', I don't recommend its use.  'fmap'
-- cannot do much optimization without knowing something about the
-- function being mapped.  Maybe better not to have the 'Functor'
-- instance.
data Reactive a = R a (Tweaks a)

-- Maybe punt Reactive and rely on the first stream element to have a Konst

instance Functor Reactive where
  fmap f (R a ts) = R b (fmapR' f b ts) where b = f a

-- Carry value along to re-use for identity edits.
fmapR' :: (a -> b) -> b -> Stream (Tweak a) -> Stream (Tweak b)
fmapR' f b (Cons (Tweak Idy _) ts) = Cons (Tweak Idy b) (fmapR' f b ts)
fmapR' f _ (Cons (Tweak _ a) ts) =
  Cons (tweak b') (fmapR' f b' ts) where b' = f a

-- | Zip up two reactive values into a reactive pair.
zipR :: Reactive a -> Reactive c -> Reactive (a,c)
zipR (R a tas) (R c tcs) = R (a,c) (zipTs tas tcs)

-- | Split a reactive pair into a pair of reactive values.
unzipR :: Reactive (a,c) -> (Reactive a, Reactive c)
unzipR (R (a,c) ts) = (R a tsa, R c tsc)
 where
   (tsa,tsc) = unzipTs ts


-- | Arrow between two reactive values.
newtype ArrowReactive (~>) a b = AR (Reactive a ~> Reactive b)

instance Category (~>) => Category (ArrowReactive (~>)) where
  id          = AR id
  AR g . AR f = AR (g . f)

instance Arrow (~>) => Arrow (ArrowReactive (~>)) where
  arr f         = AR (arr (fmap f))
  first (AR  f) = AR (arr unzipR >>> first f >>> arr (uncurry zipR))


-- | 'Automaton'-style tweak arrow.  Could be split into follow and lead.
data AutR (~>) a b = AutR (a ~> (b, Automaton (~>) (Tweak a) (Tweak b)))

instance Arrow (~>) => Category (AutR (~>)) where
  id = arr id
  AutR g . AutR f = AutR $ proc a -> do (b,ab) <- f -< a
                                        (c,bc) <- g -< b
                                        returnA -< (c, bc . ab)

instance Arrow (~>) => Arrow (AutR (~>)) where
  arr f = AutR (arr (\ a -> let b = f a in (b, arrAut f (f a))))
  first (AutR f) = AutR $
    proc (a,c) ->
      do (b,ab) <- f -< a
         returnA -< ((b,c), arr unzipT >>> first ab >>> arr (uncurry zipT))

arrAut :: Arrow (~>) => (a -> b) -> b -> Automaton (~>) (Tweak a) (Tweak b)
arrAut f b = aut
 where
   aut = Automaton (arr g)
   g (Tweak Idy _) = (Tweak Idy b, aut)
   g (Tweak _   a) = (tweak b', arrAut f b') where b' = f a