{-# LANGUAGE TypeFamilies #-} ----------------------------------------------------------------------------- -- Copyright 2015, Open Universiteit Nederland. This file is distributed -- under the terms of the GNU General Public License. For more information, -- see the file "LICENSE.txt", which is included in the distribution. ----------------------------------------------------------------------------- -- | -- Maintainer : bastiaan.heeren@ou.nl -- Stability : provisional -- Portability : portable (depends on ghc) -- -- Processes must support choices and sequences. This module defines a type -- class, an implementation, and utility functions. -- ----------------------------------------------------------------------------- -- $Id: Process.hs 7524 2015-04-08 07:31:15Z bastiaan $ module Ideas.Common.Strategy.Process ( -- * IsProcess type class IsProcess(..) -- * Process data type , Process, menu, eqProcessBy -- * Building sequences , Builder -- * Query functions on a Process , ready, stopped, firsts -- * Higher-order functions for iterating over a Process , fold, accum, scan, prune ) where import Ideas.Common.Strategy.Choice import Ideas.Common.Strategy.Sequence ------------------------------------------------------------------------ -- IsProcess type class class (Choice f, Sequence f) => IsProcess f where -- | Convert to the 'Process' data type. toProcess :: f a -> Process a ------------------------------------------------------------------------ -- Process data type -- | This datatype implements choices and sequences, but is slow for -- building sequences with the '<*>' combinator. See the 'Builder' data -- type for a faster alternative. newtype Process a = P (Menu (MenuItem a (Process a))) instance Eq a => Eq (Process a) where (==) = eqProcessBy (==) instance Functor Process where fmap f (P m) = P (fmap g m) where g Done = Done g (a :~> p) = f a :~> fmap f p instance Choice Process where single a = P (single (a :~> P (single Done))) empty = P empty P x <|> P y = P (x <|> y) P x >|> P y = P (x >|> y) P x |> P y = P (x |> y) instance Sequence Process where done = P (return Done) a ~> p = P (return (a :~> p)) p0 <*> P rest = rec p0 where rec (P m) = P $ do st <- m -- cutOn (menuItem True (\_ _ -> False)) m case st of a :~> p -> return (a :~> rec p) Done -> rest instance IsProcess Process where toProcess = id instance Firsts (Process a) where type Elem (Process a) = a menu (P m) = m -- | Generalized equality of processes, which takes an equality function for -- the symbols. eqProcessBy :: (a -> a -> Bool) -> Process a -> Process a -> Bool eqProcessBy eq = rec where rec p q = eqMenuBy eqStep (menu p) (menu q) eqStep (a :~> p) (b :~> q) = eq a b && rec p q eqStep Done Done = True eqStep _ _ = False ------------------------------------------------------------------------ -- Building sequences -- | The 'Builder' data type offers a fast implementation for building -- sequences. The data type implements the 'IsProcess' type class. -- A 'Builder' value must be converted to a 'Process' (with 'toProcess') -- it can be inspected in any way. newtype Builder a = B (Process a -> Process a) instance Choice Builder where single a = B (a ~>) empty = B (const empty) B f <|> B g = B (\p -> f p <|> g p) B f >|> B g = B (\p -> f p >|> g p) B f |> B g = B (\p -> f p |> g p) instance Sequence Builder where done = B id a ~> B f = B ((a ~>) . f) B f <*> B g = B (f . g) instance IsProcess Builder where toProcess (B f) = f done ------------------------------------------------------------------------ -- Higher-order functions for iterating over a Process -- | Folding over a process takes a function for single steps and for 'done'. {-# INLINE fold #-} fold :: Choice f => (a -> f b -> f b) -> f b -> Process a -> f b fold op e = rec where rec = onMenu (menuItem e (\a -> op a . rec)) . menu {-# INLINE accum #-} accum :: (a -> b -> [b]) -> b -> Process a -> Menu b accum f = rec where rec b p = menu p >>= g where g Done = single b g (a :~> q) = choice [ rec b2 q | b2 <- f a b ] {-# INLINE scan #-} scan :: (s -> a -> [(s, b)]) -> s -> Process a -> Process b scan op = rec where rec s = let f a q = choice [ b ~> rec s2 q | (s2, b) <- op s a ] in onMenu (menuItem done f) . menu -- fail early prune :: (a -> Bool) -> Process a -> Process a prune f = fold op done where op a p | not (f a) && stopped np = empty | otherwise = a ~> np where np = P (cut (menu p))