diff --git a/hedgehog-quickcheck/src/Hedgehog/Gen/QuickCheck.hs b/hedgehog-quickcheck/src/Hedgehog/Gen/QuickCheck.hs index 99bc9a45..6d87d3b5 100644 --- a/hedgehog-quickcheck/src/Hedgehog/Gen/QuickCheck.hs +++ b/hedgehog-quickcheck/src/Hedgehog/Gen/QuickCheck.hs @@ -16,7 +16,7 @@ import qualified Test.QuickCheck.Random as QuickCheck seedQCGen :: MonadGen m => m QuickCheck.QCGen seedQCGen = - QuickCheck.mkQCGen <$> Gen.lift (Gen.integral_ Range.constantBounded) + QuickCheck.mkQCGen <$> fromGenT (Gen.integral_ Range.constantBounded) -- | Create a Hedgehog 'Gen' from a QuickCheck 'QuickCheck.Gen'. -- diff --git a/hedgehog/src/Hedgehog.hs b/hedgehog/src/Hedgehog.hs index 51002ddc..ef065d8b 100644 --- a/hedgehog/src/Hedgehog.hs +++ b/hedgehog/src/Hedgehog.hs @@ -56,8 +56,6 @@ module Hedgehog ( , forAll , forAllWith - , classify - , cover , discard , check @@ -112,6 +110,8 @@ module Hedgehog ( , evalExceptT -- * Coverage + , classify + , cover , label , collect diff --git a/hedgehog/src/Hedgehog/Gen.hs b/hedgehog/src/Hedgehog/Gen.hs index 108f36ac..5dca45cc 100644 --- a/hedgehog/src/Hedgehog/Gen.hs +++ b/hedgehog/src/Hedgehog/Gen.hs @@ -1,9 +1,6 @@ module Hedgehog.Gen ( - -- * Combinators - lift - -- ** Shrinking - , shrink + shrink , prune -- ** Size diff --git a/hedgehog/src/Hedgehog/Internal/Gen.hs b/hedgehog/src/Hedgehog/Internal/Gen.hs index 492c8f91..cb381293 100644 --- a/hedgehog/src/Hedgehog/Internal/Gen.hs +++ b/hedgehog/src/Hedgehog/Internal/Gen.hs @@ -24,7 +24,6 @@ module Hedgehog.Internal.Gen ( , MonadGen(..) -- * Combinators - , lift , generalize -- ** Shrinking @@ -136,10 +135,10 @@ module Hedgehog.Internal.Gen ( , evalGenT , mapGenT , generate - , tree - , liftTree - , liftTreeT - , liftTreeMaybeT + , observe + , fromTree + , fromTreeT + , fromTreeMaybeT , runDiscardEffect , runDiscardEffectT @@ -172,19 +171,18 @@ import qualified Control.Monad.Morph as Morph import Control.Monad.Primitive (PrimMonad(..)) import Control.Monad.Reader.Class (MonadReader(..)) import Control.Monad.State.Class (MonadState(..)) -import Control.Monad.Trans.Class (MonadTrans) -import qualified Control.Monad.Trans.Class as Trans -import Control.Monad.Trans.Except (ExceptT(..), mapExceptT) -import Control.Monad.Trans.Identity (IdentityT(..), mapIdentityT) -import Control.Monad.Trans.Maybe (MaybeT(..), mapMaybeT) -import Control.Monad.Trans.Reader (ReaderT(..), mapReaderT) +import Control.Monad.Trans.Class (MonadTrans(..)) +import Control.Monad.Trans.Except (ExceptT(..)) +import Control.Monad.Trans.Identity (IdentityT(..)) +import Control.Monad.Trans.Maybe (MaybeT(..)) +import Control.Monad.Trans.Reader (ReaderT(..)) import Control.Monad.Trans.Resource (MonadResource(..)) import qualified Control.Monad.Trans.Writer.Lazy as Lazy import qualified Control.Monad.Trans.Writer.Strict as Strict import Control.Monad.Writer.Class (MonadWriter(..)) import Control.Monad.Zip (MonadZip(..)) -import Data.Bifunctor (first, second) +import Data.Bifunctor (first) import Data.ByteString (ByteString) import qualified Data.ByteString as ByteString import qualified Data.Char as Char @@ -266,25 +264,32 @@ mapGenT f gen = -- | Lift a predefined shrink tree in to a generator, ignoring the seed and the -- size. -- -liftTree :: Tree a -> Gen a -liftTree = - lift . liftTreeT +fromTree :: Tree a -> Gen a +fromTree = + fromGenT . fromTreeT -- | Lift a predefined shrink tree in to a generator, ignoring the seed and the -- size. -- -liftTreeT :: Monad m => TreeT m a -> GenT m a -liftTreeT x = +fromTreeT :: Monad m => TreeT m a -> GenT m a +fromTreeT x = GenT $ \_ _ -> hoist (MaybeT . fmap Just) x -- | Lift a predefined shrink tree in to a generator, ignoring the seed and the -- size. -- -liftTreeMaybeT :: TreeT (MaybeT m) a -> GenT m a -liftTreeMaybeT x = +fromTreeMaybeT :: TreeT (MaybeT m) a -> GenT m a +fromTreeMaybeT x = GenT (\_ _ -> x) +-- | +-- size. +-- +toTreeMaybeT :: MonadGen m => m a -> m (TreeT (MaybeT (GenBase m)) a) +toTreeMaybeT = + withGenT (mapGenT pure) + -- | Lazily run the discard effects through the tree and reify it a -- @Maybe (Tree a)@. -- @@ -314,99 +319,44 @@ generalize :: Monad m => Gen a -> GenT m a generalize = hoist Morph.generalize --- | Observe a generator's shrink tree. --- -tree :: Gen a -> Gen (Tree a) -tree gen = - lift . - GenT $ \size seed -> do - mnode <- Trans.lift . Trans.lift . runMaybeT . runTreeT $ runGenT size seed gen - case mnode of - Nothing -> - empty - Just node -> - case runDiscardEffect (Tree.fromNodeT node) of - Nothing -> - empty - Just tree_ -> - pure tree_ - ------------------------------------------------------------------------ -- MonadGen -- | Class of monads which can generate input data for tests. -- --- /The functions on this class can, and should, be used without their @Gen@/ --- /suffix by importing "Hedgehog.Gen" qualified./ --- class (Monad m, Monad (GenBase m)) => MonadGen m where type GenBase m :: (* -> *) + -- | Extract a 'GenT' from a 'MonadGen'. + -- toGenT :: m a -> GenT (GenBase m) a - -- | See @Gen.@'Hedgehog.Gen.lift' + -- | Lift a 'GenT' in to a 'MonadGen'. -- fromGenT :: GenT (GenBase m) a -> m a - -- | See @Gen.@'Hedgehog.Gen.shrink' - -- - shrinkGen :: (a -> [a]) -> m a -> m a - - -- | See @Gen.@'Hedgehog.Gen.prune' - -- - pruneGen :: m a -> m a - - -- | See @Gen.@'Hedgehog.Gen.scale' - -- - scaleGen :: (Size -> Size) -> m a -> m a - - -- | See @Gen.@'Hedgehog.Gen.freeze' - -- - freezeGen :: m a -> m (a, m a) +-- | Transform a 'MonadGen' as a 'GenT'. +-- +withGenT :: (MonadGen m, MonadGen n) => (GenT (GenBase m) a -> GenT (GenBase n) b) -> m a -> n b +withGenT f = + fromGenT . f . toGenT instance Monad m => MonadGen (GenT m) where + -- | The type of the transformer stack's base 'Monad'. + -- type GenBase (GenT m) = m + -- | Convert a 'MonadGen' to a 'GenT'. + -- toGenT = id + -- | Convert a 'GenT' to a 'MonadGen'. + -- fromGenT = id - shrinkGen = - mapGenT . Tree.expand - - pruneGen = - mapGenT Tree.prune - - scaleGen f gen = - GenT $ \size0 seed -> - let - size = - f size0 - in - if size < 0 then - error "Hedgehog.Gen.scale: negative size" - else - runGenT size seed gen - - freezeGen gen = - GenT $ \size seed -> do - mx <- Trans.lift . Trans.lift . runMaybeT . runTreeT $ runGenT size seed gen - case mx of - Nothing -> - empty - Just (NodeT x xs) -> - pure (x, liftTreeMaybeT . Tree.fromNodeT $ NodeT x xs) - ---listGen range gen = --- sized $ \size -> --- ensure (atLeast $ Range.lowerBound size range) $ do --- k <- integral_ range --- ts <- replicateM k (tree gen) --- liftTree $ Tree.interleave ts - instance MonadGen m => MonadGen (IdentityT m) where type GenBase (IdentityT m) = IdentityT (GenBase m) @@ -417,37 +367,6 @@ instance MonadGen m => MonadGen (IdentityT m) where fromGenT = hoist fromGenT . distributeT - shrinkGen f = - mapIdentityT (shrink f) - - pruneGen = - hoist prune - - scaleGen f = - hoist (scale f) - - freezeGen = - mapIdentityT $ - fmap (second Trans.lift) . freeze - -shrinkMaybe :: (a -> [a]) -> Maybe a -> [Maybe a] -shrinkMaybe f = \case - Nothing -> - pure Nothing - Just x -> - fmap Just (f x) - -shrinkEither :: (a -> [a]) -> Either x a -> [Either x a] -shrinkEither f = \case - Left x -> - pure $ Left x - Right x -> - fmap Right (f x) - -shrink2 :: (a -> [a]) -> (a, b) -> [(a, b)] -shrink2 f (x, y) = - fmap (, y) (f x) - instance MonadGen m => MonadGen (MaybeT m) where type GenBase (MaybeT m) = MaybeT (GenBase m) @@ -458,21 +377,6 @@ instance MonadGen m => MonadGen (MaybeT m) where fromGenT = hoist fromGenT . distributeT - shrinkGen f = - mapMaybeT $ - shrink (shrinkMaybe f) - - pruneGen = - hoist prune - - scaleGen f = - hoist (scale f) - - freezeGen = - mapMaybeT $ \m0 -> do - (mx, m) <- freeze m0 - pure $ fmap (, MaybeT m) mx - instance MonadGen m => MonadGen (ExceptT x m) where type GenBase (ExceptT x m) = ExceptT x (GenBase m) @@ -483,21 +387,6 @@ instance MonadGen m => MonadGen (ExceptT x m) where fromGenT = hoist fromGenT . distributeT - shrinkGen f = - mapExceptT $ - shrink (shrinkEither f) - - pruneGen = - hoist prune - - scaleGen f = - hoist (scale f) - - freezeGen = - mapExceptT $ \m0 -> do - (mx, m) <- freeze m0 - pure $ fmap (, ExceptT m) mx - instance MonadGen m => MonadGen (ReaderT r m) where type GenBase (ReaderT r m) = ReaderT r (GenBase m) @@ -508,19 +397,6 @@ instance MonadGen m => MonadGen (ReaderT r m) where fromGenT = hoist fromGenT . distributeT - shrinkGen f = - mapReaderT (shrink f) - - pruneGen = - hoist prune - - scaleGen f = - hoist (scale f) - - freezeGen = - mapReaderT $ - fmap (second Trans.lift) . freeze - instance (MonadGen m, Monoid w) => MonadGen (Lazy.WriterT w m) where type GenBase (Lazy.WriterT w m) = Lazy.WriterT w (GenBase m) @@ -531,21 +407,6 @@ instance (MonadGen m, Monoid w) => MonadGen (Lazy.WriterT w m) where fromGenT = hoist fromGenT . distributeT - shrinkGen f = - Lazy.mapWriterT $ - shrink (shrink2 f) - - pruneGen = - hoist prune - - scaleGen f = - hoist (scale f) - - freezeGen m0 = - Lazy.WriterT $ do - ((x, w), m) <- freeze (Lazy.runWriterT m0) - pure ((x, Lazy.WriterT m), w) - instance (MonadGen m, Monoid w) => MonadGen (Strict.WriterT w m) where type GenBase (Strict.WriterT w m) = Strict.WriterT w (GenBase m) @@ -556,111 +417,6 @@ instance (MonadGen m, Monoid w) => MonadGen (Strict.WriterT w m) where fromGenT = hoist fromGenT . distributeT - shrinkGen f = - Strict.mapWriterT $ - shrink (shrink2 f) - - pruneGen = - hoist prune - - scaleGen f = - hoist (scale f) - - freezeGen m0 = - Strict.WriterT $ do - ((x, w), m) <- freeze (Strict.runWriterT m0) - pure ((x, Strict.WriterT m), w) - --- --- The following @MonadGen@ instances require a @MonadTransControl GenT@ which --- I suspect is impossible to implement. --- ---instance MonadGen m => MonadGen (Lazy.StateT s m) where --- type GenBase (Lazy.StateT s m) = --- Lazy.StateT s (GenBase m) --- --- toGenT = --- distributeT . hoist toGenT --- --- fromGenT = --- Trans.lift . fromGenT --- --- shrinkGen f = --- Lazy.mapStateT $ --- shrink (shrink2 f) --- --- pruneGen = --- hoist prune --- --- scaleGen f = --- hoist (scale f) --- --- freezeGen m0 = --- Lazy.StateT $ \s0 -> do --- ((x, s), m) <- freeze (Lazy.runStateT m0 s0) --- pure ((x, Lazy.StateT (const m)), s) --- ---instance MonadGen m => MonadGen (Strict.StateT s m) where --- fromGenT = --- Trans.lift . fromGenT --- --- shrinkGen f = --- Strict.mapStateT $ --- shrink (shrink2 f) --- --- pruneGen = --- hoist prune --- --- scaleGen f = --- hoist (scale f) --- --- freezeGen m0 = --- Strict.StateT $ \s0 -> do --- ((x, s), m) <- freeze (Strict.runStateT m0 s0) --- pure ((x, Strict.StateT (const m)), s) --- --- shrink3 :: (a -> [a]) -> (a, b, c) -> [(a, b, c)] --- shrink3 f (x, y, z) = --- fmap (, y, z) (f x) --- ---instance (MonadGen m, Monoid w) => MonadGen (Lazy.RWST r w s m) where --- fromGenT = --- Trans.lift . fromGenT --- --- shrinkGen f = --- Lazy.mapRWST $ --- shrink (shrink3 f) --- --- pruneGen = --- hoist prune --- --- scaleGen f = --- hoist (scale f) --- --- freezeGen m0 = --- Lazy.RWST $ \r s0 -> do --- ((x, s, w), m) <- freeze (Lazy.runRWST m0 r s0) --- pure ((x, Lazy.RWST (\_ _ -> m)), s, w) --- ---instance (MonadGen m, Monoid w) => MonadGen (Strict.RWST r w s m) where --- fromGenT = --- Trans.lift . fromGenT --- --- shrinkGen f = --- Strict.mapRWST $ --- shrink (shrink3 f) --- --- pruneGen = --- hoist prune --- --- scaleGen f = --- hoist (scale f) --- --- freezeGen m0 = --- Strict.RWST $ \r s0 -> do --- ((x, s, w), m) <- freeze (Strict.runRWST m0 r s0) --- pure ((x, Strict.RWST (\_ _ -> m)), s, w) - ------------------------------------------------------------------------ -- GenT instances @@ -685,7 +441,8 @@ instance Functor m => Functor (GenT m) where -- instance Monad m => Applicative (GenT m) where pure = - liftTreeMaybeT . pure + fromTreeMaybeT . pure + (<*>) f m = GenT $ \ size seed -> case Seed.split seed of @@ -699,7 +456,7 @@ instance Monad m => Applicative (GenT m) where -- --instance Monad m => Applicative (GenT m) where -- pure = --- liftTreeMaybeT . pure +-- fromTreeMaybeT . pure -- (<*>) f m = -- GenT $ \ size seed -> -- case Seed.split seed of @@ -728,12 +485,13 @@ instance Monad m => MonadFail (GenT m) where instance Monad m => Alternative (GenT m) where empty = mzero + (<|>) = mplus instance Monad m => MonadPlus (GenT m) where mzero = - liftTreeMaybeT mzero + fromTreeMaybeT mzero mplus x y = GenT $ \size seed -> @@ -744,42 +502,50 @@ instance Monad m => MonadPlus (GenT m) where instance MonadTrans GenT where lift = - liftTreeMaybeT . Trans.lift . Trans.lift + fromTreeMaybeT . lift . lift instance MFunctor GenT where hoist f = mapGenT (hoist (hoist f)) -embedMaybe :: +embedMaybeT :: MonadTrans t => Monad n => Monad (t (MaybeT n)) => (forall a. m a -> t (MaybeT n) a) -> MaybeT m b -> t (MaybeT n) b -embedMaybe f m = - Trans.lift . MaybeT . pure =<< f (runMaybeT m) - -embedTree :: Monad n => (forall a. m a -> TreeT (MaybeT n) a) -> TreeT (MaybeT m) b -> TreeT (MaybeT n) b -embedTree f tree_ = - embed (embedMaybe f) tree_ - -embedGen :: Monad n => (forall a. m a -> GenT n a) -> GenT m b -> GenT n b -embedGen f gen = +embedMaybeT f m = + lift . MaybeT . pure =<< f (runMaybeT m) + +embedTreeMaybeT :: + Monad n + => (forall a. m a -> TreeT (MaybeT n) a) + -> TreeT (MaybeT m) b + -> TreeT (MaybeT n) b +embedTreeMaybeT f tree_ = + embed (embedMaybeT f) tree_ + +embedGenT :: + Monad n + => (forall a. m a -> GenT n a) + -> GenT m b + -> GenT n b +embedGenT f gen = GenT $ \size seed -> case Seed.split seed of (sf, sg) -> - (runGenT size sf . f) `embedTree` + (runGenT size sf . f) `embedTreeMaybeT` (runGenT size sg gen) instance MMonad GenT where embed = - embedGen + embedGenT -distributeGen :: Transformer t GenT m => GenT (t m) a -> t (GenT m) a -distributeGen x = - join . Trans.lift . GenT $ \size seed -> - pure . hoist liftTreeMaybeT . distributeT . hoist distributeT $ runGenT size seed x +distributeGenT :: Transformer t GenT m => GenT (t m) a -> t (GenT m) a +distributeGenT x = + join . lift . GenT $ \size seed -> + pure . hoist fromTreeMaybeT . distributeT . hoist distributeT $ runGenT size seed x instance MonadTransDistributive GenT where type Transformer t GenT m = ( @@ -789,26 +555,26 @@ instance MonadTransDistributive GenT where ) distributeT = - distributeGen + distributeGenT instance PrimMonad m => PrimMonad (GenT m) where type PrimState (GenT m) = PrimState m primitive = - Trans.lift . primitive + lift . primitive instance MonadIO m => MonadIO (GenT m) where liftIO = - Trans.lift . liftIO + lift . liftIO instance MonadBase b m => MonadBase b (GenT m) where liftBase = - Trans.lift . liftBase + lift . liftBase instance MonadThrow m => MonadThrow (GenT m) where throwM = - Trans.lift . throwM + lift . throwM instance MonadCatch m => MonadCatch (GenT m) where catch m onErr = @@ -820,23 +586,23 @@ instance MonadCatch m => MonadCatch (GenT m) where instance MonadReader r m => MonadReader r (GenT m) where ask = - Trans.lift ask + lift ask local f m = mapGenT (local f) m instance MonadState s m => MonadState s (GenT m) where get = - Trans.lift get + lift get put = - Trans.lift . put + lift . put state = - Trans.lift . state + lift . state instance MonadWriter w m => MonadWriter w (GenT m) where writer = - Trans.lift . writer + lift . writer tell = - Trans.lift . tell + lift . tell listen = undefined --mapGenT listen @@ -846,7 +612,7 @@ instance MonadWriter w m => MonadWriter w (GenT m) where instance MonadError e m => MonadError e (GenT m) where throwError = - Trans.lift . throwError + lift . throwError catchError m onErr = GenT $ \size seed -> case Seed.split seed of @@ -856,17 +622,11 @@ instance MonadError e m => MonadError e (GenT m) where instance MonadResource m => MonadResource (GenT m) where liftResourceT = - Trans.lift . liftResourceT + lift . liftResourceT ------------------------------------------------------------------------ -- Combinators --- | Lift a vanilla 'Gen' in to a 'MonadGen'. --- -lift :: MonadGen m => GenT (GenBase m) a -> m a -lift = - fromGenT - -- | Generate a value with no shrinks from a 'Size' and a 'Seed'. -- generate :: MonadGen m => (Size -> Seed -> a) -> m a @@ -883,14 +643,20 @@ generate f = -- the existing shrinks intact. -- shrink :: MonadGen m => (a -> [a]) -> m a -> m a -shrink = - shrinkGen +shrink f = + withGenT $ mapGenT (Tree.expand f) -- | Throw away a generator's shrink tree. -- prune :: MonadGen m => m a -> m a prune = - pruneGen + withGenT $ mapGenT Tree.prune + +-- | Observe a generator's shrink tree. +-- +observe :: forall m a. (MonadGen m, GenBase m ~ Identity) => m a -> m (Tree a) +observe = + withGenT $ mapGenT (Maybe.maybe empty pure . runDiscardEffect) ------------------------------------------------------------------------ -- Combinators - Size @@ -911,8 +677,17 @@ resize size gen = -- | Adjust the size parameter by transforming it with the given function. -- scale :: MonadGen m => (Size -> Size) -> m a -> m a -scale = - scaleGen +scale f = + withGenT $ \gen -> + GenT $ \size0 seed -> + let + size = + f size0 + in + if size < 0 then + error "Hedgehog.Gen.scale: negative size" + else + runGenT size seed gen -- | Make a generator smaller by scaling its size parameter. -- @@ -1471,33 +1246,24 @@ maybe gen = , (1 + fromIntegral n, Just <$> gen) ] -interleaveList :: Monad m => [TreeT m a] -> m (NodeT m [a]) -interleaveList = - fmap Tree.interleave . traverse runTreeT - ---freezeTree :: MonadGen m => m a -> m (TreeT (MaybeT m) a) - -freezeTree :: MonadGen m => m a -> m (TreeT (MaybeT (GenBase m)) a) -freezeTree = - fromGenT . mapGenT pure . toGenT - -- | Generates a list using a 'Range' to determine the length. -- list :: MonadGen m => Range Int -> m a -> m [a] list range gen = let - -- TODO: Doesn't respect the minimum bound on the length - go :: Monad m => TreeT (MaybeT m) [TreeT (MaybeT m) a] -> TreeT (MaybeT m) [a] - go = - TreeT . (interleaveList . nodeValue =<<) . runTreeT + --interleave :: Monad m => TreeT (MaybeT m) [TreeT (MaybeT m) a] -> TreeT (MaybeT m) [a] + interleave = + (interleaveTreeT . nodeValue =<<) in sized $ \size -> ensure (atLeast $ Range.lowerBound size range) . - fromGenT . - mapGenT go . - toGenT $ do + withGenT (mapGenT (TreeT . interleave . runTreeT)) $ do n <- integral_ range - replicateM n (freezeTree gen) + replicateM n (toTreeMaybeT gen) + +interleaveTreeT :: Monad m => [TreeT m a] -> m (NodeT m [a]) +interleaveTreeT = + fmap Tree.interleave . traverse runTreeT -- | Generates a seq using a 'Range' to determine the length. -- @@ -1610,7 +1376,14 @@ deriving instance Traversable (Vec n) -- freeze :: MonadGen m => m a -> m (a, m a) freeze = - freezeGen + withGenT $ \gen -> + GenT $ \size seed -> do + mx <- lift . lift . runMaybeT . runTreeT $ runGenT size seed gen + case mx of + Nothing -> + empty + Just (NodeT x xs) -> + pure (x, fromGenT . fromTreeMaybeT . Tree.fromNodeT $ NodeT x xs) shrinkSubterms :: Subterms n a -> [Subterms n a] shrinkSubterms = \case diff --git a/hedgehog/src/Hedgehog/Internal/Property.hs b/hedgehog/src/Hedgehog/Internal/Property.hs index daffe519..073606c6 100644 --- a/hedgehog/src/Hedgehog/Internal/Property.hs +++ b/hedgehog/src/Hedgehog/Internal/Property.hs @@ -1096,7 +1096,7 @@ label name = withFrozenCallStack $ cover 0 name True --- | Like 'label', but uses the 'Show'n value as the label. +-- | Like 'label', but uses 'Show' to render its argument for display. -- collect :: (MonadTest m, Show a, HasCallStack) => a -> m () collect x = diff --git a/hedgehog/src/Hedgehog/Internal/Tree.hs b/hedgehog/src/Hedgehog/Internal/Tree.hs index 9c0f7e10..7178479f 100644 --- a/hedgehog/src/Hedgehog/Internal/Tree.hs +++ b/hedgehog/src/Hedgehog/Internal/Tree.hs @@ -12,6 +12,7 @@ module Hedgehog.Internal.Tree ( Tree , TreeT(..) , runTree + , mapTreeT , treeValue , treeChildren @@ -94,6 +95,12 @@ runTree :: Tree a -> Node a runTree = runIdentity . runTreeT +-- | Map between 'TreeT' computations. +-- +mapTreeT :: (m (NodeT m a) -> m (NodeT m a)) -> TreeT m a -> TreeT m a +mapTreeT f = + TreeT . f . runTreeT + -- | Create a 'TreeT' from a 'NodeT' -- fromNodeT :: Applicative m => NodeT m a -> TreeT m a