{-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UnicodeSyntax #-} {-| This module contains infrastructure for working with 'Location's, which indicate a location within a tree but, unlike 'Path', without the cached values. -} module LogicGrowsOnTrees.Location ( -- * Type-classes MonadLocatable(..) -- * Types , Location , Solution(..) , LocatableT(..) , LocatableTree , LocatableTreeIO , LocatableTreeT(..) -- * Utility functions , applyCheckpointCursorToLocation , applyContextToLocation , applyPathToLocation , branchingFromLocation , labelFromBranching , labelFromContext , labelFromPath , leftBranchOf , locationTransformerForBranchChoice , normalizeLocatableTree , normalizeLocatableTreeT , rightBranchOf , rootLocation , runLocatableT , sendTreeDownLocation , sendTreeTDownLocation , solutionsToMap -- * Exploration functions , exploreLocatableTree , exploreLocatableTreeT , exploreLocatableTreeTAndIgnoreResults , exploreTreeWithLocations , exploreTreeTWithLocations , exploreTreeWithLocationsStartingAt , exploreTreeTWithLocationsStartingAt , exploreLocatableTreeUntilFirst , exploreLocatableTreeUntilFirstT , exploreTreeUntilFirstWithLocation , exploreTreeTUntilFirstWithLocation , exploreTreeUntilFirstWithLocationStartingAt , exploreTreeTUntilFirstWithLocationStartingAt ) where import Control.Applicative (Alternative(..),Applicative(..)) import Control.Exception (throw) import Control.Monad (MonadPlus(..),(>=>),liftM,liftM2) import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad.Operational (ProgramViewT(..),viewT) import Control.Monad.Trans.Class (MonadTrans(..)) import Control.Monad.Trans.Reader (ReaderT(..),ask) import Data.Composition import qualified Data.Map as Map import Data.Map (Map) import Data.Maybe (fromJust) import Data.Monoid import Data.Foldable as Fold import Data.Function (on) import Data.Functor.Identity (Identity,runIdentity) import Data.Sequence (viewl,ViewL(..)) import Data.SequentialIndex (SequentialIndex,root,leftChild,rightChild) import LogicGrowsOnTrees import LogicGrowsOnTrees.Checkpoint import LogicGrowsOnTrees.Path -------------------------------------------------------------------------------- --------------------------------- Type-classes --------------------------------- -------------------------------------------------------------------------------- {-| The class 'MonadLocatable' allows you to get your current location within a tree. -} class MonadPlus m ⇒ MonadLocatable m where getLocation :: m Location -------------------------------------------------------------------------------- ------------------------------------ Types ------------------------------------- -------------------------------------------------------------------------------- {-| A 'Location' identifies a location in a tree; unlike 'Path' it only contains information about the list of branches that have been taken, and not information about the cached values encounted along the way. -} newtype Location = Location { unwrapLocation :: SequentialIndex } deriving (Eq) {-| A 'Solution' is a result tagged with the location of the leaf at which it was found. -} data Solution α = Solution { solutionLocation :: Location , solutionResult :: α } deriving (Eq,Ord,Show) {-| The 'Monoid' instance constructs a location that is the result of appending the path in the second argument to the path in the first argument. -} instance Monoid Location where mempty = rootLocation xl@(Location x) `mappend` yl@(Location y) | x == root = yl | y == root = xl | otherwise = Location $ go y root x where go original_label current_label product_label = case current_label `compare` original_label of EQ → product_label -- Note: the following is counter-intuitive, but it makes sense if you think of it as -- being where you need to go to get to the original label instead of where you -- currently are with respect to the original label GT → (go original_label `on` (fromJust . leftChild)) current_label product_label LT → (go original_label `on` (fromJust . rightChild)) current_label product_label {-| The 'Ord' instance performs the comparison using the list of branches in the path defined by the location, which is obtained using the function 'branchingFromLocation'. -} instance Ord Location where compare = compare `on` branchingFromLocation instance Show Location where show = fmap (\branch → case branch of {LeftBranch → 'L'; RightBranch → 'R'}) . branchingFromLocation {-| 'LocatableT' is a monad transformer that allows you to take any MonadPlus and add to it the ability to tell where you are in the tree created by the 'mplus's. -} newtype LocatableT m α = LocatableT { unwrapLocatableT :: ReaderT Location m α } deriving (Applicative,Functor,Monad,MonadIO,MonadTrans) instance (Alternative m, Monad m) ⇒ Alternative (LocatableT m) where empty = LocatableT $ lift empty LocatableT left <|> LocatableT right = LocatableT . ReaderT $ \branch → (runReaderT left (leftBranchOf branch)) <|> (runReaderT right (rightBranchOf branch)) instance MonadPlus m ⇒ MonadLocatable (LocatableT m) where getLocation = LocatableT $ ask instance MonadPlus m ⇒ MonadPlus (LocatableT m) where mzero = LocatableT $ lift mzero LocatableT left `mplus` LocatableT right = LocatableT . ReaderT $ \branch → (runReaderT left (leftBranchOf branch)) `mplus` (runReaderT right (rightBranchOf branch)) instance MonadExplorableTrans m ⇒ MonadExplorableTrans (LocatableT m) where type NestedMonad (LocatableT m) = NestedMonad m runAndCache = LocatableT . lift . runAndCache runAndCacheGuard = LocatableT . lift . runAndCacheGuard runAndCacheMaybe = LocatableT . lift . runAndCacheMaybe instance MonadPlus m ⇒ Monoid (LocatableT m α) where mempty = mzero mappend = mplus {-| A 'Tree' augmented with the ability to get the current location -} type LocatableTree = LocatableTreeT Identity {-| Like 'LocatableTree', but running in the IO monad. -} type LocatableTreeIO = LocatableTreeT IO {-| Like 'LocatableTree', but running in an arbitrary monad. -} newtype LocatableTreeT m α = LocatableTreeT { unwrapLocatableTreeT :: LocatableT (TreeT m) α } deriving (Alternative,Applicative,Functor,Monad,MonadIO,MonadLocatable,MonadPlus,Monoid) instance MonadTrans LocatableTreeT where lift = LocatableTreeT . lift . lift instance Monad m ⇒ MonadExplorableTrans (LocatableTreeT m) where type NestedMonad (LocatableTreeT m) = m runAndCache = LocatableTreeT . runAndCache runAndCacheGuard = LocatableTreeT . runAndCacheGuard runAndCacheMaybe = LocatableTreeT . runAndCacheMaybe -------------------------------------------------------------------------------- ---------------------------------- Functions ----------------------------------- -------------------------------------------------------------------------------- ------------------------------ Utility functions ------------------------------- {-| Append the path indicated by a checkpoint cursor to the given location's path. -} applyCheckpointCursorToLocation :: CheckpointCursor {-^ a path within the subtree -} → Location {-^ the location of the subtree -} → Location {-^ the location within the full tree obtained by following the path to the subtree and then the path indicated by the checkpoint cursor -} applyCheckpointCursorToLocation cursor = case viewl cursor of EmptyL → id step :< rest → applyCheckpointCursorToLocation rest . case step of CachePointD _ → id ChoicePointD active_branch _ → locationTransformerForBranchChoice active_branch {-| Append the path indicated by a context to the given location's path. -} applyContextToLocation :: Context m α {-^ the path within the subtree -} → Location {-^ the location of the subtree -} → Location {-^ the location within the full tree obtained by following the path to the subtree and then the path indicated by the context -} applyContextToLocation context = case viewl context of EmptyL → id step :< rest → applyContextToLocation rest . case step of CacheContextStep _ → id LeftBranchContextStep _ _ → leftBranchOf RightBranchContextStep → rightBranchOf {-| Append a path to a location's path. -} applyPathToLocation :: Path {-^ a path within the subtree -} → Location {-^ the location of the subtree -} → Location {-^ the location within the full tree obtained by following the path to the subtree and then the given path -} applyPathToLocation path = case viewl path of EmptyL → id step :< rest → applyPathToLocation rest . case step of ChoiceStep active_branch → locationTransformerForBranchChoice active_branch CacheStep _ → id {-| Converts a location to a list of branch choices. -} branchingFromLocation :: Location → [BranchChoice] branchingFromLocation = go root . unwrapLocation where go current_label original_label = case current_label `compare` original_label of EQ → [] GT → LeftBranch:go (fromJust . leftChild $ current_label) original_label LT → RightBranch:go (fromJust . rightChild $ current_label) original_label {-| Converts a list (or other 'Foldable') of branch choices to a location. -} labelFromBranching :: Foldable t ⇒ t BranchChoice → Location labelFromBranching = Fold.foldl' (flip locationTransformerForBranchChoice) rootLocation {-| Contructs a 'Location' representing the location within the tree indicated by the 'Context'. -} labelFromContext :: Context m α → Location labelFromContext = flip applyContextToLocation rootLocation {-| Contructs a 'Location' representing the location within the tree indicated by the 'Path'. -} labelFromPath :: Path → Location labelFromPath = flip applyPathToLocation rootLocation {-| Returns the 'Location' at the left branch of the given location. -} leftBranchOf :: Location → Location leftBranchOf = Location . fromJust . leftChild . unwrapLocation {-| Convenience function takes a branch choice and returns a location transformer that appends the branch choice to the given location. -} locationTransformerForBranchChoice :: BranchChoice → (Location → Location) locationTransformerForBranchChoice LeftBranch = leftBranchOf locationTransformerForBranchChoice RightBranch = rightBranchOf {-| Converts a 'LocatableTree' to a 'Tree'. -} normalizeLocatableTree :: LocatableTree α → Tree α normalizeLocatableTree = runLocatableT . unwrapLocatableTreeT {-| Converts a 'LocatableTreeT' to a 'TreeT'. -} normalizeLocatableTreeT :: LocatableTreeT m α → TreeT m α normalizeLocatableTreeT = runLocatableT . unwrapLocatableTreeT {-| Returns the 'Location' at the right branch of the given location. -} rightBranchOf :: Location → Location rightBranchOf = Location . fromJust . rightChild . unwrapLocation {-| The location at the root of the tree. -} rootLocation :: Location rootLocation = Location root {-| Runs a 'LocatableT' to obtain the nested monad. -} runLocatableT :: LocatableT m α → m α runLocatableT = flip runReaderT rootLocation . unwrapLocatableT {-| Walks down a 'Tree' to the subtree at the given 'Location'. This function is analogous to 'LogicGrowsOnTrees.Path.sendTreeDownPath', and shares the same caveats. -} sendTreeDownLocation :: Location → Tree α → Tree α sendTreeDownLocation label = runIdentity . sendTreeTDownLocation label {-| Like 'sendTreeDownLocation', but for impure trees. -} sendTreeTDownLocation :: Monad m ⇒ Location → TreeT m α → m (TreeT m α) sendTreeTDownLocation (Location label) = go root where go parent tree | parent == label = return tree | otherwise = (viewT . unwrapTreeT) tree >>= \view → case view of Return _ → throw TreeEndedBeforeEndOfWalk Null :>>= _ → throw TreeEndedBeforeEndOfWalk ProcessPendingRequests :>>= k → go parent . TreeT . k $ () Cache mx :>>= k → mx >>= maybe (throw TreeEndedBeforeEndOfWalk) (go parent . TreeT . k) Choice left right :>>= k → if parent > label then go (fromJust . leftChild $ parent) (left >>= TreeT . k) else go (fromJust . rightChild $ parent) (right >>= TreeT . k) {-| Converts a list (or other 'Foldable') of solutions to a 'Map' from 'Location's to results. -} solutionsToMap :: Foldable t ⇒ t (Solution α) → Map Location α solutionsToMap = Fold.foldl' (flip $ \(Solution label solution) → Map.insert label solution) Map.empty ------------------------------ Exploration functions ------------------------------- {-| Explore all the nodes in a 'LocatableTree' and sum over all the results in the leaves. -} exploreLocatableTree :: Monoid α ⇒ LocatableTree α → α exploreLocatableTree = exploreTree . runLocatableT . unwrapLocatableTreeT {-| Same as 'exploreLocatableTree', but for an impure tree. -} exploreLocatableTreeT :: (Monoid α,Monad m) ⇒ LocatableTreeT m α → m α exploreLocatableTreeT = exploreTreeT . runLocatableT . unwrapLocatableTreeT {-| Same as 'exploreLocatableTree', but the results are discarded so the tree is only explored for its side-effects. -} exploreLocatableTreeTAndIgnoreResults :: Monad m ⇒ LocatableTreeT m α → m () exploreLocatableTreeTAndIgnoreResults = exploreTreeTAndIgnoreResults . runLocatableT . unwrapLocatableTreeT {-| Explores all of the nodes of a tree, returning a list of solutions each tagged with the location at which it was found. -} exploreTreeWithLocations :: Tree α → [Solution α] exploreTreeWithLocations = runIdentity . exploreTreeTWithLocations {-| Like 'exploreTreeWithLocations' but for an impure tree. -} exploreTreeTWithLocations :: Monad m ⇒ TreeT m α → m [Solution α] exploreTreeTWithLocations = exploreTreeTWithLocationsStartingAt rootLocation {-| Like 'exploreTreeWithLocations', but for a subtree whose location is given by the first argument; the solutions are labeled by the /absolute/ location within the full tree (as opposed to their relative location within the subtree). -} exploreTreeWithLocationsStartingAt :: Location → Tree α → [Solution α] exploreTreeWithLocationsStartingAt = runIdentity .* exploreTreeTWithLocationsStartingAt {-| Like 'exploreTreeWithLocationsStartingAt' but for an impure trees. -} exploreTreeTWithLocationsStartingAt :: Monad m ⇒ Location → TreeT m α → m [Solution α] exploreTreeTWithLocationsStartingAt label = viewT . unwrapTreeT >=> \view → case view of Return x → return [Solution label x] Cache mx :>>= k → mx >>= maybe (return []) (exploreTreeTWithLocationsStartingAt label . TreeT . k) Choice left right :>>= k → liftM2 (++) (exploreTreeTWithLocationsStartingAt (leftBranchOf label) $ left >>= TreeT . k) (exploreTreeTWithLocationsStartingAt (rightBranchOf label) $ right >>= TreeT . k) Null :>>= _ → return [] ProcessPendingRequests :>>= k → exploreTreeTWithLocationsStartingAt label . TreeT . k $ () {-| Explores all the nodes in a 'LocatableTree' until a result (i.e., a leaf) has been found; if a result has been found then it is returned wrapped in 'Just', otherwise 'Nothing' is returned. -} exploreLocatableTreeUntilFirst :: LocatableTree α → Maybe α exploreLocatableTreeUntilFirst = exploreTreeUntilFirst . runLocatableT . unwrapLocatableTreeT {-| Like 'exploreLocatableTreeUntilFirst' but for an impure tree. -} exploreLocatableTreeUntilFirstT :: Monad m ⇒ LocatableTreeT m α → m (Maybe α) exploreLocatableTreeUntilFirstT = exploreTreeTUntilFirst . runLocatableT . unwrapLocatableTreeT {-| Explores all the nodes in a tree until a result (i.e., a leaf) has been found; if a result has been found then it is returned tagged with the location at which it was found and wrapped in 'Just', otherwise 'Nothing' is returned. -} exploreTreeUntilFirstWithLocation :: Tree α → Maybe (Solution α) exploreTreeUntilFirstWithLocation = runIdentity . exploreTreeTUntilFirstWithLocation {-| Like 'exploreTreeUntilFirstWithLocation' but for an impure tree. -} exploreTreeTUntilFirstWithLocation :: Monad m ⇒ TreeT m α → m (Maybe (Solution α)) exploreTreeTUntilFirstWithLocation = exploreTreeTUntilFirstWithLocationStartingAt rootLocation {-| Like 'exploreTreeUntilFirstWithLocation', but for a subtree whose location is given by the first argument; the solution (if present) is labeled by the /absolute/ location within the full tree (as opposed to its relative location within the subtree). -} exploreTreeUntilFirstWithLocationStartingAt :: Location → Tree α → Maybe (Solution α) exploreTreeUntilFirstWithLocationStartingAt = runIdentity .* exploreTreeTUntilFirstWithLocationStartingAt {-| Like 'exploreTreeUntilFirstWithLocationStartingAt' but for an impure tree. -} exploreTreeTUntilFirstWithLocationStartingAt :: Monad m ⇒ Location → TreeT m α → m (Maybe (Solution α)) exploreTreeTUntilFirstWithLocationStartingAt = go .* exploreTreeTWithLocationsStartingAt where go = liftM $ \solutions → case solutions of [] → Nothing (x:_) → Just x