{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE UnicodeSyntax #-} {-| This modules contains functionality relating to paths through trees. -} module LogicGrowsOnTrees.Path ( -- * Types BranchChoice(..) , Step(..) , Path -- * Functions , oppositeBranchChoiceOf , sendTreeDownPath , sendTreeTDownPath -- * Exceptions , WalkError(..) ) where import Control.Exception (Exception(),throw) import Control.Monad.Operational (ProgramViewT(..),viewT) import Data.ByteString (ByteString) import Data.Derive.Serialize import Data.DeriveTH import Data.Functor.Identity (runIdentity) import Data.Sequence (Seq,viewl,ViewL(..)) import Data.Serialize import Data.Typeable (Typeable) import LogicGrowsOnTrees -------------------------------------------------------------------------------- ---------------------------------- Exceptions ---------------------------------- -------------------------------------------------------------------------------- {-| This exception is thrown whenever a 'Tree' is sent down a path which is incompatible with it. -} data WalkError = {-| Indicates that a path is too long for a given tree --- that is, the walk hit a leaf (or a null) before the end of the path was reached. -} TreeEndedBeforeEndOfWalk {-| Indicates that a choice step in a path coincided with a cache point in a tree, or vice versa. -} | PastTreeIsInconsistentWithPresentTree deriving (Eq,Show,Typeable) instance Exception WalkError -------------------------------------------------------------------------------- ------------------------------------- Types ------------------------------------ -------------------------------------------------------------------------------- {-| A choice at a branch point to take either the left branch or the right branch. -} data BranchChoice = LeftBranch | RightBranch deriving (Eq,Ord,Read,Show) $( derive makeSerialize ''BranchChoice ) {-| A step in a path through a tree, which can either pass through a point with a cached result or take a choice to go left or right at a branch point. -} data Step = CacheStep ByteString {-^ Step through a cache point -} | ChoiceStep BranchChoice {-^ Step through a choice point -} deriving (Eq,Ord,Show) $( derive makeSerialize ''Step ) {-| A sequence of 'Step's. -} type Path = Seq Step -------------------------------------------------------------------------------- ---------------------------------- Functions ----------------------------------- -------------------------------------------------------------------------------- {-| Returns the opposite of the given branch choice. -} oppositeBranchChoiceOf :: BranchChoice → BranchChoice oppositeBranchChoiceOf LeftBranch = RightBranch oppositeBranchChoiceOf RightBranch = LeftBranch {-| Follows a 'Path' through a 'Tree' to a particular subtree; the main use case of this function is for a processor which has been given a particular subtree as its workload to zoom in on that subtree. The way this function works is as follows: as long as the remaining path is non-empty, it explores the 'Tree' until it encounters either a cache point or a choice point; in the former case the path supplies the cached value in the 'CacheStep' constructor, and in the latter case the path supplies the branch to take in the 'ChoiceStep' constructor; when the remaining path is empty then the resulting 'Tree' is returned. WARNING: This function is /not/ valid for all inputs; it makes the assumption that the given 'Path' has been derived from the given 'Tree' so that the path will always encountered choice points exactly when the tree does and likewise for cache points. Furthermore, the path must not run out before the tree hits a leaf. If any of these conditions is violated, a 'WalkError' exception will be thrown; in fact, you should hope than exception is thrown because it will let you know that there is a bug your code as the alternative is that you accidently give it a path that is not derived from the given tree but which coincidentally matches it which means that it will silently return a nonsensical result. Having said all that, you should almost never need to worry about this possibility in practice because there will normally be only one tree in use at a time and all paths in use will have come from that tree. -} sendTreeDownPath :: Path → Tree α → Tree α sendTreeDownPath path = runIdentity . sendTreeTDownPath path {-| Like 'sendTreeDownPath', but for impure trees. -} sendTreeTDownPath :: Monad m ⇒ Path → TreeT m α → m (TreeT m α) sendTreeTDownPath path tree = case viewl path of EmptyL → return tree step :< tail → do view ← viewT . unwrapTreeT $ tree case (view,step) of (Return _,_) → throw TreeEndedBeforeEndOfWalk (Null :>>= _,_) → throw TreeEndedBeforeEndOfWalk (Cache _ :>>= k,CacheStep cache) → sendTreeTDownPath tail $ either error (TreeT . k) (decode cache) (Choice left _ :>>= k,ChoiceStep LeftBranch) → sendTreeTDownPath tail (left >>= TreeT . k) (Choice _ right :>>= k,ChoiceStep RightBranch) → sendTreeTDownPath tail (right >>= TreeT . k) (ProcessPendingRequests :>>= k,_) → sendTreeTDownPath path (TreeT . k $ ()) _ → throw PastTreeIsInconsistentWithPresentTree