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