diff --git a/rtree/.gitignore b/rtree/.gitignore index 508061b4ec51f5d62e7c585ce5f3d10fdf5dc48f..6b62980548b8e79703f2f4a9ca5e062237f80261 100644 --- a/rtree/.gitignore +++ b/rtree/.gitignore @@ -1 +1,2 @@ actual +.hspec-failures diff --git a/rtree/.hspec b/rtree/.hspec new file mode 100644 index 0000000000000000000000000000000000000000..31179283b5b6ebc68352f2ba9e7cc8f2d35d6a19 --- /dev/null +++ b/rtree/.hspec @@ -0,0 +1,4 @@ +--failure-report .hspec-failures +--rerun +--rerun-all-on-success +--fail-fast diff --git a/rtree/src/Control/Monad/RTree.hs b/rtree/src/Control/Monad/RTree.hs index f64aa26e207903cf5b554877c30632e025489d7e..18cc8168be768d2dea082b8d6dc446937b1db18b 100644 --- a/rtree/src/Control/Monad/RTree.hs +++ b/rtree/src/Control/Monad/RTree.hs @@ -7,6 +7,7 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StrictData #-} {-# LANGUAGE UndecidableInstances #-} -- | A naive implementation of the rtree. @@ -24,26 +25,29 @@ module Control.Monad.RTree ( extractT, reduceT, RTreeN (..), - unStateT, flattenT, -- * Re-exports module Control.Monad.Reduce, + module Data.RPath, ) where import Control.Applicative import Control.Monad import Control.Monad.Identity import Control.Monad.Reduce -import Control.Monad.State +import Control.Monad.Trans +import Control.Monad.Trans.Maybe (MaybeT (runMaybeT)) import Data.Foldable import Data.Foldable.WithIndex import Data.Function ((&)) +import Data.RPath +import qualified Data.Sequence as Seq -- | The simple RTree data RTree i = Done !i - | Split (RTree i) !(RTree i) + | Split ~(RTree i) !(RTree i) deriving (Functor, Foldable) instance Applicative RTree where @@ -60,9 +64,9 @@ instance MonadReduce RTree where instance FoldableWithIndex RPath RTree where ifoldMap f = - [] & fix \rec rs -> \case - Done i -> f (fromChoiceList (reverse rs)) i - Split lhs rhs -> rec (False : rs) lhs <> rec (True : rs) rhs + Seq.empty & fix \rec rs -> \case + Done i -> f (fromChoiceList (toList rs)) i + Split lhs rhs -> rec (rs Seq.|> True) lhs <> rec (rs Seq.|> False) rhs -- | Extract the top value from the RTree. extract :: RTree i -> i @@ -96,11 +100,15 @@ drawRTree pp = concat . go ] -- | Reduce the tree -reduce :: (MonadPlus m) => (i -> m Bool) -> RTree i -> m i -reduce p = checkgo +reduce + :: (Monad m) + => (i -> m Bool) + -> RTree i + -> m (Maybe i) +reduce p = runMaybeT . checkgo where checkgo r = do - t <- p (extract r) + t <- lift $ p (extract r) guard t *> go r go = \case Done i -> pure i @@ -129,9 +137,6 @@ instance (Monad m) => Monad (RTreeT m) where instance (Monad m) => MonadReduce (RTreeT m) where split lhs rhs = RTreeT (pure $ SplitN lhs rhs) -instance (MonadState s m) => MonadState s (RTreeT m) where - state f = RTreeT (DoneN <$> state f) - -- | Extract a value from an @RTreeT@ extractT :: (Monad m) => RTreeT m b -> m b extractT (RTreeT m) = m >>= extractN @@ -143,13 +148,6 @@ extractN = \case SplitN _ rhs -> extractT rhs {-# INLINE extractN #-} -unStateT :: (Monad m) => RTreeT (StateT s m) i -> s -> RTreeT m (i, s) -unStateT (RTreeT (StateT sf)) s = RTreeT do - (t, s') <- sf s - case t of - DoneN i -> pure $ DoneN (i, s') - SplitN lhs rhs -> pure $ SplitN (unStateT lhs s') (unStateT rhs s') - flattenT :: RTreeT Identity i -> RTree i flattenT (RTreeT (Identity t)) = case t of DoneN i -> Done i diff --git a/rtree/src/Control/Monad/Reduce.hs b/rtree/src/Control/Monad/Reduce.hs index dd401199d88afba3858f077edf02316c01d33274..ae55bb931e7bcdc6294319fe6d2b82f111ba6f8e 100644 --- a/rtree/src/Control/Monad/Reduce.hs +++ b/rtree/src/Control/Monad/Reduce.hs @@ -27,11 +27,6 @@ module Control.Monad.Reduce ( collectNonEmpty, collectNonEmpty', - -- * RPath - RPath, - fromChoiceList, - toChoiceList, - -- * Helpers onBoth, liftMaybe, @@ -40,13 +35,11 @@ module Control.Monad.Reduce ( import Control.Applicative import Control.Monad +import Control.Monad.Reader import Control.Monad.State import Control.Monad.Trans.Maybe -import Data.Bool import qualified Data.List.NonEmpty as NE import Data.Maybe -import Data.String -import qualified Data.Vector.Unboxed as VU -- {- | A reducer should extract itself -- @ @@ -60,19 +53,16 @@ import qualified Data.Vector.Unboxed as VU class (Monad m) => MonadReduce m where {-# MINIMAL (split | check) #-} - -- | Split the world into the a reduced world (left) without an element and a world - -- with that element (right). Optionally, labeled with l. split :: m i -> m i -> m i split r1 r2 = check >>= \case - False -> r1 - True -> r2 + True -> r1 + False -> r2 {-# INLINE split #-} - -- | Check with returns a boolean, that can be used to split the input into a world where - -- the optional truth assignement is satisfiable and where it is not. + -- | Check if the predicate is true. check :: m Bool - check = split (pure False) (pure True) + check = split (pure True) (pure False) {-# INLINE check #-} -- | Infix split. @@ -135,25 +125,9 @@ onBoth mlhs mrhs fn = Just rhs -> fn lhs rhs instance (MonadReduce m) => MonadReduce (StateT s m) where - check = StateT (\s -> split (pure (False, s)) (pure (True, s))) + check = StateT (\s -> split (pure (True, s)) (pure (False, s))) {-# INLINE check #-} -{- | A reduction path, can be used as an index into reduction tree. -Is isomorphic to a list of choices. --} -newtype RPath = RPath {rPathAsVector :: VU.Vector Bool} - deriving (Eq, Ord) - --- | Create a reduction path from a list of choices -fromChoiceList :: [Bool] -> RPath -fromChoiceList = RPath . VU.fromList - --- | Get a list of choices from a reduction path. -toChoiceList :: RPath -> [Bool] -toChoiceList = VU.toList . rPathAsVector - -instance Show RPath where - show = show . map (bool '0' '1') . toChoiceList - -instance IsString RPath where - fromString = fromChoiceList . map (== '1') +instance (MonadReduce m) => MonadReduce (ReaderT r m) where + check = ReaderT (\_ -> split (pure True) (pure False)) + {-# INLINE check #-} diff --git a/rtree/src/Data/RPath.hs b/rtree/src/Data/RPath.hs new file mode 100644 index 0000000000000000000000000000000000000000..9d8bf616057444bceebbf86f8a5a64d4449c61c5 --- /dev/null +++ b/rtree/src/Data/RPath.hs @@ -0,0 +1,54 @@ +module Data.RPath ( + -- * RPath + RPath, + fromChoiceList, + toChoiceList, + + -- * As a predicate + toPredicate, + toPredicateDebug, +) where + +import Data.Bool +import Data.IORef (atomicModifyIORef, newIORef) +import Data.Maybe +import Data.String +import qualified Data.Vector.Unboxed as VU + +{- | A reduction path, can be used as an index into reduction tree. +Is isomorphic to a list of choices. +-} +newtype RPath = RPath {rPathAsVector :: VU.Vector Bool} + deriving (Eq, Ord) + +toPredicate :: RPath -> IO (i -> IO Bool) +toPredicate (RPath v) = do + idx <- newIORef (-1) + pure . const $ do + idx' <- atomicModifyIORef idx (\n -> (n + 1, n)) + pure (fromMaybe True (v VU.!? idx')) + +toPredicateDebug :: (Show i) => RPath -> IO (i -> IO Bool) +toPredicateDebug rp@(RPath v) = do + idx <- newIORef (-1) + pure $ \i -> do + idx' <- atomicModifyIORef idx (\n -> (n + 1, n)) + print (rp, idx', i) + pure (fromMaybe True (v VU.!? idx')) + +-- | Create a reduction path from a list of choices +fromChoiceList :: [Bool] -> RPath +fromChoiceList = RPath . VU.fromList + +-- | Get a list of choices from a reduction path. +toChoiceList :: RPath -> [Bool] +toChoiceList = VU.toList . rPathAsVector + +instance Show RPath where + show = show . map (bool '0' '1') . toChoiceList + +instance Read RPath where + readsPrec i s = [(fromString a, r) | (a, r) <- readsPrec i s] + +instance IsString RPath where + fromString = fromChoiceList . map (== '1') diff --git a/rtree/test/expected/rlist-drawrtree b/rtree/test/expected/rlist-drawrtree new file mode 100644 index 0000000000000000000000000000000000000000..71b5ad7bec1fa26e6d0202047b04de41a2b6e43e --- /dev/null +++ b/rtree/test/expected/rlist-drawrtree @@ -0,0 +1,8 @@ +┳â”┳â”┳┠[1,2,3] +┃ ┃ â”—â” [1,2] +┃ â”—â”┳┠[1,3] +┃ â”—â” [1] +â”—â”┳â”┳┠[2,3] + ┃ â”—â” [2] + â”—â”┳┠[3] + â”—â” [] diff --git a/rtree/test/src/Control/Monad/RTreeSpec.hs b/rtree/test/src/Control/Monad/RTreeSpec.hs index 79a8d12605bb0bebdaafeba12394dbc6b785bc7f..b4433f14c47c97c0bcdc2cd28bb7e01f2cd1d87d 100644 --- a/rtree/test/src/Control/Monad/RTreeSpec.hs +++ b/rtree/test/src/Control/Monad/RTreeSpec.hs @@ -6,11 +6,11 @@ module Control.Monad.RTreeSpec where -import Control.Applicative import Control.Monad.Identity (Identity (runIdentity)) import Control.Monad.RTree import Control.Monad.State import Control.Monad.Trans.Maybe +import Data.Expr as Expr import Data.Foldable import Data.Functor import qualified Data.Map.Strict as Map @@ -27,71 +27,11 @@ rBool = split (pure False) (pure True) rList :: (MonadReduce m) => [a] -> m [a] rList = collect (given $>) -data Expr - = Var !String - | Cnt !Int - | Opr !Expr !Expr - | Let !String !Expr !Expr - deriving (Show, Eq) - -rExpr - :: (MonadReducePlus m, MonadState (Map.Map String (Either String Expr)) m) - => Expr - -> m Expr -rExpr e = case e of - Cnt i -> do - given $> Cnt i - Var k -> do - v <- liftMaybe =<< gets (Map.lookup k) - case v of - Left k' -> given $> Var k' - Right x -> rExpr x - Opr e1 e2 -> onBoth (rExpr e1) (rExpr e2) \e1' e2' -> - case (e1', e2') of - (Cnt a, Cnt b) -> pure (Cnt (a + b)) <| pure (Opr e1' e2') - _ow -> pure $ Opr e1' e2' - Let k e1 e2 -> do - e1' <- rExpr e1 - split - (modifyIn (Map.insert k (Right e1')) $ rExpr e2) - (Let k e1' <$> modifyIn (Map.insert k (Left k)) (rExpr e2)) - -modifyIn :: (Alternative m, MonadState s m) => (s -> s) -> m b -> m b -modifyIn fn mx = do - s <- get - put (fn s) - x <- optional mx - put s - liftMaybe x - -prettyExprS :: Int -> Expr -> String -> String -prettyExprS d = \case - Var x -> showString x - Opr l r -> - showParen (d > addPrec) $ - prettyExprS (addPrec + 1) l - . showString " + " - . prettyExprS (addPrec + 1) r - Cnt i -> shows i - Let x e1 e2 -> - showParen (d > letPrec) $ - showString x - . showString " := " - . prettyExprS (letPrec + 1) e1 - . showString "; " - . prettyExprS letPrec e2 - where - addPrec = 2 - letPrec = 1 - -prettyExprWithConfig :: (Maybe Expr, Map.Map String (Either String Expr)) -> String -prettyExprWithConfig (e, _) = - maybe "⊥" (flip (prettyExprS 0) "") e - spec :: Spec spec = do rtreeSpec rtreeTSpec + examplesSpec rtreeTSpec :: Spec rtreeTSpec = describe "RTreeT" do @@ -102,29 +42,6 @@ rtreeTSpec = describe "RTreeT" do it "should input like RTree" do equiv (rList [1, 2, 3 :: Int]) inputs (toList :: RTreeT Identity [Int] -> [[Int]]) - describe "rExpr" do - let handle str e = describe str do - let me = runMaybeT $ rExpr e - it "should extract" do - extract (runStateT me Map.empty) - `shouldBe` (Just e, Map.empty) - it "should draw the same" do - golden - ("test/expected/" <> str) - (drawRTree prettyExprWithConfig (runStateT me Map.empty)) - - handle "small-opr-expr" $ - Opr (Cnt 1) (Cnt 2) - - handle "small-let-expr" $ - Let "x" (Cnt 1) (Opr (Cnt 2) (Var "x")) - - handle "double-let-expr" $ - Let "x" (Cnt 1) (Let "y" (Cnt 2) (Opr (Var "x") (Var "y"))) - - handle "double-overloading-let-expr" $ - Let "x" (Cnt 1) (Let "x" (Cnt 2) (Opr (Var "x") (Var "x"))) - equiv :: (Show b, MonadReduce x, MonadReduce y, Eq b) => (forall m. (MonadReduce m) => m a) @@ -154,12 +71,51 @@ rtreeSpec = describe "RTree" do describe "iinputs" do it "should get all inputs from rList" do iinputs (rList [1, 2, 3 :: Int]) - `shouldBe` [ ("000", []) - , ("001", [3]) - , ("010", [2]) - , ("011", [2, 3]) - , ("100", [1]) - , ("101", [1, 3]) - , ("110", [1, 2]) - , ("111", [1, 2, 3]) + `shouldBe` [ ("111", []) + , ("110", [3]) + , ("101", [2]) + , ("100", [2, 3]) + , ("011", [1]) + , ("010", [1, 3]) + , ("001", [1, 2]) + , ("000", [1, 2, 3]) ] + + describe "drawRTree" do + it "should pretty print it's tree" do + golden + "test/expected/rlist-drawrtree" + (drawRTree show (rList [1, 2, 3 :: Int])) + +examplesSpec :: Spec +examplesSpec = do + describe "rExpr" do + handle "small-opr-expr" $ + Opr (Cnt 1) (Cnt 2) + + handle "small-let-expr" $ + Let "x" (Cnt 1) (Opr (Cnt 2) (Var "x")) + + handle "double-let-expr" $ + Let "x" (Cnt 1) (Let "y" (Cnt 2) (Opr (Var "x") (Var "y"))) + + handle "double-overloading-let-expr" $ + Let "x" (Cnt 1) (Let "x" (Cnt 2) (Opr (Var "x") (Var "x"))) + where + handle str e = describe (str <> " (" <> prettyExprS 0 e ")") do + let me = runMaybeT $ Expr.rExpr e + it "should extract" do + extract (runStateT me Map.empty) + `shouldBe` (Just e, Map.empty) + + let re = evalStateT me Map.empty + + it "should draw the same" do + golden + ("test/expected/" <> str) + (drawRTree (maybe "⊥" (flip (prettyExprS 0) "")) re) + + it "should reduce like iinputs" do + forM_ (iinputs re) \(ii, e') -> do + p <- toPredicate ii + reduce p re `shouldReturn` Just e' diff --git a/rtree/test/src/Data/Expr.hs b/rtree/test/src/Data/Expr.hs new file mode 100644 index 0000000000000000000000000000000000000000..48ea44d9948f14f95e7b06854967a705803fab8f --- /dev/null +++ b/rtree/test/src/Data/Expr.hs @@ -0,0 +1,67 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE LambdaCase #-} + +module Data.Expr where + +import Control.Applicative +import Control.Monad.Reduce +import Control.Monad.State +import Data.Functor +import qualified Data.Map.Strict as Map + +data Expr + = Var !String + | Cnt !Int + | Opr !Expr !Expr + | Let !String !Expr !Expr + deriving (Show, Eq) + +rExpr + :: (MonadReducePlus m, MonadState (Map.Map String (Either String Expr)) m) + => Expr + -> m Expr +rExpr e = case e of + Cnt i -> do + given $> Cnt i + Var k -> do + v <- liftMaybe =<< gets (Map.lookup k) + case v of + Left k' -> given $> Var k' + Right x -> rExpr x + Opr e1 e2 -> onBoth (rExpr e1) (rExpr e2) $ \e1' e2' -> + case (e1', e2') of + (Cnt a, Cnt b) -> pure (Cnt (a + b)) <| pure (Opr e1' e2') + _ow -> pure $ Opr e1' e2' + Let k e1 e2 -> do + e1' <- rExpr e1 + split + (modifyIn (Map.insert k (Right e1')) $ rExpr e2) + (Let k e1' <$> modifyIn (Map.insert k (Left k)) (rExpr e2)) + +modifyIn :: (Alternative m, MonadState s m) => (s -> s) -> m b -> m b +modifyIn fn mx = do + s <- get + put (fn s) + x <- optional mx + put s + liftMaybe x + +prettyExprS :: Int -> Expr -> String -> String +prettyExprS d = \case + Var x -> showString x + Opr l r -> + showParen (d > addPrec) $ + prettyExprS (addPrec + 1) l + . showString " + " + . prettyExprS (addPrec + 1) r + Cnt i -> shows i + Let x e1 e2 -> + showParen (d > letPrec) $ + showString x + . showString " := " + . prettyExprS (letPrec + 1) e1 + . showString "; " + . prettyExprS letPrec e2 + where + addPrec = 2 + letPrec = 1