packages feed

bot-0.0: src/Data/Bot/LeadFollow.hs

{-# LANGUAGE TypeOperators, GeneralizedNewtypeDeriving, ScopedTypeVariables #-}
----------------------------------------------------------------------
-- |
-- Module      :  Data.Bot.LeadFollow
-- Copyright   :  (c) Conal Elliott 2008
-- License     :  BSD3
-- 
-- Maintainer  :  conal@conal.net
-- Stability   :  experimental
-- 
-- Functional reactive programming as an interactive dance of alternating
-- lead and follow.  See <http://conal.net/blog/tag/dance/> for
-- explanation and @Examples.LeadFollow@ for examples.
----------------------------------------------------------------------

module Data.Bot.LeadFollow
  ( -- * Lead and follow -- single-output
    Lead(..), Follow(..), lead, follow
  , scanlF1, scanlL1, accumF1, accumL1
    -- * Lead and follow -- multi-output
  , (:>-)(..), (:->)(..)
  , follow1, lead1, leads, follows
  , splitL, followL, initL
    -- * Filtering
  , justF, filterF
    -- * Accumulation
  , scanlF, scanlL, accumF, accumL
  ) where

import Control.Applicative
import Control.Arrow hiding (pure)
import Data.Maybe (maybeToList)
import Data.Monoid



{--------------------------------------------------------------------
    Lead and follow -- single-output
--------------------------------------------------------------------}

-- | Respond to inputs, leading to start.
-- 
-- Isomorphic to @a -> (b, a -> (b, a -> (b, ...)))@
newtype a  `Lead`  b = Lead   { unLead   :: (b ,  a `Follow` b) }

-- | Respond to inputs, following to start.
-- 
-- Isomorphic to @(b, a -> (b, a -> (b, a -> ...)))@
newtype a `Follow` b = Follow { unFollow ::  a -> a  `Lead`  b  }

-- | Start out leading
lead :: b -> a `Follow` b -> a `Lead` b
lead = curry Lead

-- | Start out following
follow :: (a -> a `Lead` b) -> a `Follow` b
follow = Follow

-- instance Functor ((`Follow`) a) where
--   fmap f (Follow h) = Follow (fmap f . h)

-- instance Applicative ((`Follow`) a) where
--   pure b = Follow (const (pure b))
--   Follow h <*> Follow k = Follow $ \ a -> h a <*> k a

-- -- We could also write

instance Functor (Follow a) where
  fmap f (Follow h) = Follow ((fmap.fmap) f h)

instance Applicative (Follow a) where
  pure b = Follow ((pure.pure) b)
  Follow h <*> Follow k = Follow $ liftA2 (<*>) h k

instance Functor (Lead a) where
  fmap f (Lead (b, g)) = Lead (f b, fmap f g)

instance Applicative (Lead a) where
  pure b = Lead (b, pure b)
  Lead (f,pf) <*> Lead (x,px) = Lead (f x, pf <*> px)

-- The four instances above can almost be automatically generated:

-- type Follow a = (->) a :.  Lead a
-- type Lead a =   Id   :*: Follow a

-- Then the Functor and Applicative instances for free.  But we'd still
-- need a loop-breaker.  I don't know how to get GHC to derive instances
-- through newtypes in this case.

-- Adapted from the Automaton Arrow instance
instance Arrow Follow where
  arr f = foll where foll = Follow (\ a -> Lead (f a, foll))
  Follow f >>> Follow g = Follow $
    f >>>
    arr unLead >>>
    first g >>>
    arr (\ (Lead (z, cg), cf) -> Lead (z, cf >>> cg))
  first (Follow f) = Follow $
    first f >>>
    arr (\(Lead (x', c), y) -> Lead ((x', y), first c))

-- Boilerplate Monoid instances for Applicative

instance Monoid b => Monoid (Follow a b) where
  mempty  = pure   mempty
  mappend = liftA2 mappend

instance Monoid b => Monoid (Lead a b) where
  mempty  = pure   mempty
  mappend = liftA2 mappend


-- | Analog to 'scanl' -- single-output follow (no initial @b@).
scanlF1 :: (b -> a -> b) -> b -> Follow a b
scanlF1 f b = Follow $ \ a -> scanlL1 f (f b a)

-- | Analog to 'scanl' -- single-output lead (with initial @b@).
scanlL1 :: (b -> a -> b) -> b -> Lead a b
scanlL1 f b = Lead (b, scanlF1 f b)

-- | Accumulate function applications -- single-output, no initial @a@.
accumF1 :: a -> Follow (a->a) a
accumF1 = scanlF1 (flip ($))

-- | Accumulate function applications -- single-output, with initial @a@.
accumL1 :: a -> Lead (a->a) a
accumL1 = scanlL1 (flip ($))


{--------------------------------------------------------------------
    Lead and follow -- mult-output
--------------------------------------------------------------------}

-- Multiple steps
steps :: Monoid os => ([i], i `Follow` os) -> (os, i `Follow` os)
steps (is,bot) =
  first (mconcat.reverse) $ foldl step ([], bot) is
 where
   step :: ([b], a `Follow` b) -> a -> ([b], a `Follow` b)
   step (bs, Follow f) = first (:bs) . unLead . f

concatMB :: Monoid cs => Follow b cs -> Follow [b] cs
concatMB bot = Follow $ \ bs -> Lead $ second concatMB $ steps (bs,bot)


-- | Start out leading (multi-output)
newtype a :>- b =   Leads { unLeads   ::   Lead a [b] } deriving Monoid

-- | Start out following (multi-output)
newtype a :-> b = Follows { unFollows :: Follow a [b] } deriving Monoid

instance Arrow (:->) where
  arr h = Follows (arr (pure . h))
  Follows ab >>> Follows bc = Follows (ab >>> concatMB bc)
  first (Follows f) = Follows $
    first f >>> arr (\ (bs,c) -> [(b,c) | b <- bs])
    -- first f >>> arr (\ (bs,c) -> fmap (flip (,) c) bs)


-- The other instances are boilerplate for composition of applicative
-- functors.

instance Functor ((:->) i) where
  fmap f (Follows z) = Follows ((fmap.fmap) f z)

instance Functor ((:>-) i) where
  fmap f (Leads   z) = Leads   ((fmap.fmap) f z)

instance Applicative ((:->) i) where
  pure x                  = Follows ((pure.pure) x)
  Follows f <*> Follows x = Follows (liftA2 (<*>) f x)

instance Applicative ((:>-) i) where
  pure x                  = Leads   ((pure.pure) x)
  Leads   f <*> Leads   x = Leads   (liftA2 (<*>) f x)


-- | Wrap single-out follow as multi-out
follow1 :: Follow a b -> a :-> b 
follow1 = Follows . fmap pure

-- | Wrap single-out lead as multi-out
lead1   :: Lead a b -> a :>- b
lead1   = Leads   . fmap pure


-- | Start out leading
leads :: [b] -> a :-> b -> a :>- b
leads bs (Follows fol) = Leads (lead bs fol)

-- | Start out following
follows :: (a -> a :>- b) -> a :-> b
follows h = Follows (follow (unLeads . h))


-- | Split lead into initial outputs and follow
splitL :: a :>- b -> ([b], a :-> b)
splitL (Leads (Lead (bs,f))) = (bs,Follows f)

-- | Initial outputs of a lead
initL :: a :>- b -> [b]
initL = fst . splitL

-- | The follow after initial outputs
followL :: a :>- b -> a :-> b
followL = snd . splitL


{--------------------------------------------------------------------
    Filtering
--------------------------------------------------------------------}


justF :: Maybe a :-> a
justF = Follows (arr maybeToList)

filterF :: (a -> Bool) -> a :-> a
filterF p = f ^>> justF
 where
   f a | p a       = Just a
       | otherwise = Nothing


{--------------------------------------------------------------------
    Accumulation
--------------------------------------------------------------------}

-- | Analog to 'scanl', no initial @b@.
scanlF :: (b -> a -> b) -> b -> a :-> b
scanlF = (fmap.fmap) follow1 scanlF1

-- | Analog to 'scanl', with initial @b@.
scanlL :: (b -> a -> b) -> b -> a :>- b
scanlL = (fmap.fmap) lead1 scanlL1

-- | Accumulate function applications, no initial @a@.
accumF :: a -> (a->a) :-> a
accumF = scanlF (flip ($))

-- | Accumulate function applications, with initial @a@.
accumL :: a -> (a->a) :>- a
accumL = scanlL (flip ($))