peakachu-0.3.0: src/FRP/Peakachu/Program.hs
{-# LANGUAGE GeneralizedNewtypeDeriving, TemplateHaskell #-}
-- | @Program a b@ is a pure representation of a computer program,
-- which accepts inputs of type @a@, and outputs values of type @b@.
-- It may also terminate. It can output zero or more @b@ values after each @a@ input.
--
-- * A simple stateless input-output-loop can be created from a function
-- with 'arrC'.
--
-- * A simple stateful input-output-loop can be created using 'scanlP'.
--
-- * Outputs can be filtered using 'filterC'.
--
-- Programs may also be composed together in several ways using common type-classes
--
-- * 'Category': @Program a b -> Program b c -> Program a c@. One program's outputs are fed
-- to another program as input.
--
-- * 'Monoid': @Program a b -> Program a b -> Program a b@. Both programs run in parallel processing the same input. Resulting Program outputs both's outputs.
--
-- * 'Applicative': @Program a (b -> c) -> Program a b -> Program a c@.
--
-- * Alternative `MonadPlus`: 'AppendProgram' is a newtype wrapper whose `Monoid` instance runs one program after the other finishes (like `ZipList` offers an alternative `Applicative` instance for lists). It's also a `Monad` ant its monadic bind allows us to invoke inner programs based on an outer program's outputs.
module FRP.Peakachu.Program
( Program(..), AppendProgram(..)
, scanlP, emptyP, takeWhileP, loopbackP, singleValueP, lstP, lstPs, delayP
, withAppendProgram1, withAppendProgram2
) where
import Control.FilterCategory (FilterCategory(..), genericFlattenC)
import Data.ADT.Getters (mkADTGetters)
import Data.Newtype (mkWithNewtypeFuncs)
import Control.Applicative (Applicative(..), (<$>), liftA2)
import Control.Category (Category(..))
import Control.Monad (MonadPlus(..), ap)
import Data.DeriveTH (derive, makeFunctor)
import Data.List (genericDrop, genericTake)
import Data.Maybe (mapMaybe, catMaybes)
import Data.Monoid (Monoid(..))
import Prelude hiding ((.), id)
-- | A computer program
data Program a b = Program
{ progVals :: [b]
, progMore :: Maybe (a -> Program a b)
}
$(derive makeFunctor ''Program)
instance Category Program where
id =
Program [] (Just f)
where
f x = Program [x] (Just f)
left . right =
Program (catMaybes stuff >>= progVals) more
where
Program rightStart rightMore = right
stuff = scanl step (Just left) rightStart
step l valRight = do
Program _ moreLeft <- l
moreFunc <- moreLeft
return $ moreFunc valRight
more = do
moreFunc <- rightMore
lastStuff <- last stuff
return $ (.) (Program [] (progMore lastStuff)) . moreFunc
instance FilterCategory Program where
flattenC =
f []
where
f = (`Program` Just f)
arrC = (<$> id)
$(mkADTGetters ''Either)
-- | Create a stateful input-output-loop from a simple function
scanlP :: (b -> a -> b) -> b -> Program a b
scanlP step start = Program [start] $ Just (scanlP step . step start)
-- | A program that terminates immediately
emptyP :: Program a b
emptyP = Program [] Nothing
-- | Terminate when a predicate on input fails
takeWhileP :: (a -> Bool) -> Program a a
takeWhileP cond =
Program [] (Just f)
where
f x
| cond x = Program [x] (Just f)
| otherwise = Program [] Nothing
-- | Feed some outputs of a 'Program' to itself
loopbackP :: Program a (Either a b) -> Program a b
loopbackP program =
Program
{ progVals = stuff >>= mapMaybe gRight . progVals
, progMore = (fmap . fmap) loopbackP . progMore . last $ stuff
}
where
stuff =
scanl step program
. mapMaybe gLeft . progVals $ program
step prev val =
maybe emptyP ($ val) (progMore prev)
-- | A program that outputs a value and immediately terminates
singleValueP :: Program a ()
singleValueP = scanlP const () . emptyP
-- | Delay the outputs of a 'Program'
delayP :: Integral i => i -> Program a a
delayP n =
flattenC . arrC (genericDrop n) . scanlP step []
where
step xs = (: genericTake n xs)
-- would be nice to derive this.
-- but "derive" currently can't: http://code.google.com/p/ndmitchell/issues/detail?id=270&q=proj:Derive
instance Monoid (Program a b) where
mempty = Program mempty mempty
mappend left right =
Program
{ progVals = mappend (progVals left) (progVals right)
, progMore = mappend (progMore left) (progMore right)
}
instance Applicative (Program a) where
pure x =
Program
{ progVals = pure x
, progMore = (pure . pure) (pure x)
}
left <*> right =
Program
{ progVals = progVals left <*> progVals right
, progMore = (liftA2 . liftA2) (<*>) (progMore left) (progMore right)
}
-- Combine programs to run in sequence
newtype AppendProgram a b = AppendProg
{ runAppendProg :: Program a b
} deriving (Category, FilterCategory, Functor)
$(mkWithNewtypeFuncs [1,2] ''AppendProgram)
instance Monoid (AppendProgram a b) where
mempty = AppendProg emptyP
mappend (AppendProg left) (AppendProg right) =
AppendProg $
case progMore left of
Nothing -> Program
{ progVals = progVals left ++ progVals right
, progMore = progMore right
}
Just more -> Program
{ progVals = progVals left
, progMore = Just $ flip (withAppendProgram2 mappend) right <$> more
}
instance Monad (AppendProgram a) where
return x = AppendProg $ Program [x] Nothing
AppendProg left >>= right =
mconcat $ map right (progVals left) ++ [rest]
where
rest =
AppendProg Program
{ progVals = []
, progMore =
(fmap . fmap . withAppendProgram1)
(>>= right) (progMore left)
}
instance MonadPlus (AppendProgram a) where
mzero = mempty
mplus = mappend
instance Applicative (AppendProgram a) where
pure = return
(<*>) = ap
-- | Given a partial function @(a -> Maybe b)@ and a start value, output its most recent result on an input.
lstPs :: Maybe b -> (a -> Maybe b) -> Program a b
lstPs start f =
genericFlattenC . scanlP (flip mplus) start . arrC f
-- | Given a partial function @(a -> Maybe b)@, output its most recent result on an input.
lstP :: (a -> Maybe b) -> Program a b
lstP = lstPs Nothing