{-# language ScopedTypeVariables #-} {-# language DeriveGeneric #-} {-# language FlexibleContexts #-} {-# language UndecidableInstances #-} {-# language GeneralizedNewtypeDeriving #-} {-# language DeriveTraversable #-} {-# language StandaloneDeriving #-} {-# language ViewPatterns #-} module Main(main) where import Control.Monad.IO.Class (liftIO) import Hedgehog (MonadGen, Range) import qualified Hedgehog as HH import qualified Hedgehog.Gen as Gen import Hedgehog.Range (Size) import qualified Hedgehog.Range as Range import Test.Hspec (before, describe, hspec, it, shouldBe) import Test.Hspec.Hedgehog (PropertyT, diff, forAll, hedgehog, (/==), (===)) import Control.Monad.Logic.Class (MonadLogic (..)) import Control.Monad.Logic.Sequence import qualified Control.Monad.Logic.Sequence.Compat as Compat import Control.Monad.Logic.Sequence.Internal (SeqT (..)) import Data.SequenceClass hiding ((:<), empty) import qualified Data.SequenceClass as S import Control.Monad.Logic.Sequence.Internal.Queue import Data.Functor.Identity import Control.Applicative import Data.Function (fix, on) import GHC.Generics (Generic) import qualified Hedgehog.Function as Fun import Data.Foldable (foldl', for_) import qualified Control.Monad.Logic as L import Debug.Trace (trace) import Control.Monad.Trans.Maybe import Control.Monad.Reader import Control.Monad.Except import Control.Monad.Morph (hoist) import Control.Monad.ST import Text.Read (readMaybe) import Data.List (cycle) -- | A generic "container" functor. We can use this with `Free` to get -- an inspectable `Monad` that's unlikely to hide any mistakes we make. data TestF a = TestF !Int [a] deriving (Show, Read, Eq, Generic, Functor, Foldable, Traversable) instance Fun.Arg a => Fun.Arg (TestF a) -- Note: size -- -- I've found it quite difficult to get a good range of -- sizes for SeqT TestM Int using the basic tools in -- Gen. Preventing almost all examples being tiny seems to lead to -- some examples being unmanageably enormous. So I've decided to -- go with a "nuclear option". First, I select the approximate total number -- of nodes in the SeqT. Then at each stage, the approximate total size is -- chosen in advance to make sure the target is met. -- | Generate a partition of a non-negative integer into positive -- integers. This is not statistically fair because I'm not that smart. splat :: MonadGen m => Size -> m [Size] splat 0 = pure [] splat n = do k <- Gen.integral (Range.constant 1 n) rest <- splat (n - k) pure (k : rest) genTestFSized :: MonadGen m => (Size -> m a) -> Size -> m (TestF a) genTestFSized m sz = do i <- Gen.integral (Range.constant 1 10000) part <- splat sz goop <- traverse m part pure (TestF i goop) newtype TestM a = TestM (Free TestF a) deriving (Show, Read, Eq, Generic, Functor, Applicative, Monad, Foldable, Traversable) genTestMSized :: MonadGen m => (Size -> m a) -> Size -> m (TestM a) genTestMSized = \m sz -> TestM <$> go m sz where go :: MonadGen m => (Size -> m a) -> Size -> m (Free TestF a) go m n | n <= 1 = Pure <$> m (n - 1) go m n = Free <$> genTestFSized (go m) (n - 1) -- | Generate a test monad value. genTestM :: MonadGen m => m a -> m (TestM a) genTestM m = Gen.sized $ \sz -> do true_size <- Gen.integral (Range.constant 0 sz) genTestMSized (const m) true_size simpleTestM :: MonadGen m => m (TestM Int) simpleTestM = genTestM (Gen.integral $ Range.constant 0 5) listToQueue :: [a] -> Queue a listToQueue = foldl' (S.|>) S.empty genViewSized :: forall m a. MonadGen m => m a -> Size -> m (ViewT TestM a) genViewSized _ sz | sz <= 1 = pure Empty genViewSized m sz = do a <- m s <- genSeqTSized m (sz - 1) pure (a :< s) genSeqTSized :: forall m a. MonadGen m => m a -> Size -> m (SeqT TestM a) genSeqTSized m sz = do part <- splat sz goop <- traverse (genTestMSized (genViewSized m)) part pure $ SeqT $ listToQueue goop genSeqT :: forall m a. MonadGen m => m a -> m (SeqT TestM a) genSeqT m = Gen.sized $ \sz -> do tsz <- Gen.integral (Range.linear 0 sz) genSeqTSized m tsz simpleSeqT :: MonadGen m => m (SeqT TestM Int) simpleSeqT = genSeqT (Gen.integral $ Range.constant 0 5) genSeqSized :: forall m a. MonadGen m => m a -> Size -> m (Seq a) genSeqSized m sz = do part <- splat sz goop <- traverse (fmap Identity <$> genViewSizedId m) part pure $ SeqT $ listToQueue goop genViewSizedId :: forall m a. MonadGen m => m a -> Size -> m (ViewT Identity a) genViewSizedId _ sz | sz <= 1 = pure Empty genViewSizedId m sz = do a <- m s <- genSeqSized m (sz - 1) pure (a :< s) genSeq :: forall m a. MonadGen m => m a -> m (Seq a) genSeq m = Gen.sized $ \sz -> do tsz <- Gen.integral (Range.linear 0 sz) genSeqSized m tsz simpleSeq :: MonadGen m => m (Seq Int) simpleSeq = genSeq (Gen.integral $ Range.constant 0 5) main :: IO () main = hspec $ do describe "observe" $ do it "undoes pure" $ hedgehog $ observe (pure (3 :: Int)) === Just 3 describe "observeT" $ do it "undoes lift" $ hedgehog $ do ex <- forAll simpleTestM observeT (lift ex) === (Just <$> ex) describe "observeAllT" $ do it "undoes lift" $ hedgehog $ do ex <- forAll simpleTestM observeAllT (lift ex) === fmap (:[]) ex it "works like logicT" $ hedgehog $ do ex <- forAll simpleSeqT observeAllT ex === L.observeAllT (Compat.fromSeqT ex) describe "observeManyT" $ do it "takes at most n" $ hedgehog $ do n <- forAll $ Gen.integral (Range.linearFrom 0 (-100) 100) let alot :: SeqT (ST s) Int alot = pure n <|> alot length (runST (observeManyT n alot)) === max 0 n it "takes what it can" $ hedgehog $ do n <- forAll $ Gen.integral (Range.linearFrom 0 0 100) k <- forAll $ Gen.integral (Range.linearFrom 0 0 10) let alot :: Int -> SeqT (ST s) Int alot x | x <= 0 = empty alot x = pure x <|> alot (x-1) length (runST (observeManyT n (alot (n-k)))) === max 0 (n-k) it "in order" $ hedgehog $ do n <- forAll $ Gen.integral (Range.linearFrom 0 0 100) let alot :: Int -> SeqT (ST s) Int alot x | x <= 0 = empty alot x = pure x <|> alot (x-1) runST (observeManyT n (alot n)) === [n,(n-1)..1] describe "observeMany" $ do it "takes at most n" $ hedgehog $ do n <- forAll $ Gen.integral (Range.linearFrom 0 (-100) 100) let alot :: Seq Int alot = pure n <|> alot length (observeMany n alot) === max 0 n it "takes what it can" $ hedgehog $ do n <- forAll $ Gen.integral (Range.linearFrom 0 0 100) k <- forAll $ Gen.integral (Range.linearFrom 0 0 10) let alot :: Int -> Seq Int alot x | x <= 0 = empty alot x = pure x <|> alot (x-1) length (observeMany n (alot (n-k))) === max 0 (n-k) it "in order" $ hedgehog $ do n <- forAll $ Gen.integral (Range.linearFrom 0 0 100) let alot :: Int -> Seq Int alot x | x <= 0 = empty alot x = pure x <|> alot (x-1) observeMany n (alot n) === [n,(n-1)..1] describe "read" $ do it "undoes show" $ hedgehog $ do ex <- forAll simpleSeqT readMaybe (show ex) === Just ex describe ">>=" $ do it "obeys monad identity law 1" $ hedgehog $ do s <- forAll simpleSeqT (s >>= return) === s it "obeys monad identity law 2" $ hedgehog $ do a <- forAll $ Gen.integral Range.linearBounded f :: Int -> SeqT TestM Int <- Fun.forAllFn (Fun.fn simpleSeqT) (pure a >>= f) === f a it "works like LogicT" $ hedgehog $ do s <- forAll simpleSeqT f :: Int -> SeqT TestM Int <- Fun.forAllFn (Fun.fn simpleSeqT) Compat.fromLogicT (Compat.toLogicT s >>= Compat.toLogicT . f) === (s >>= f) it "obeys monad associativity law" $ hedgehog $ do s <- forAll simpleSeqT f :: Int -> SeqT TestM Int <- Fun.forAllFn (Fun.fn simpleSeqT) g :: Int -> SeqT TestM Int <- Fun.forAllFn (Fun.fn simpleSeqT) ((s >>= f) >>= g) === (s >>= \a -> f a >>= g) it "obeys left zero law" $ hedgehog $ do f :: Int -> SeqT TestM Int <- Fun.forAllFn (Fun.fn simpleSeqT) (empty >>= f) === empty describe "<|>" $ do it "is associative" $ hedgehog $ do s <- forAll (Gen.small simpleSeqT) t <- forAll (Gen.small simpleSeqT) u <- forAll (Gen.small simpleSeqT) ((s <|> t) <|> u) === (s <|> (t <|> u)) it "obeys Alternative identity laws" $ hedgehog $ do s <- forAll (Gen.small simpleSeqT) (s <|> empty) === s (empty <|> s) === s it "obeys left distribution" $ hedgehog $ do s <- forAll (Gen.small simpleSeqT) t <- forAll (Gen.small simpleSeqT) f :: Int -> SeqT TestM Int <- Fun.forAllFn (Fun.fn simpleSeqT) ((s <|> t) >>= f) === ((s >>= f) <|> (t >>= f)) it "works like LogicT" $ hedgehog $ do s <- forAll simpleSeqT t <- forAll simpleSeqT (s <|> t) === Compat.fromLogicT (Compat.fromSeqT s <|> Compat.fromSeqT t) describe "fromLogicT" $ do it "reverses fromSeqT" $ hedgehog $ do s <- forAll simpleSeqT Compat.fromLogicT (Compat.fromSeqT s) === s describe "fromViewT" $ do it "reverses toViewT" $ hedgehog $ do s <- forAll simpleSeqT fromViewT (toViewT s) === s describe "MonadReader instance" $ do it "passes the tests in https://github.com/Bodigrim/logict/issues/1" $ do runReader (runMaybeT (observeAllT (local (5+) ask))) 0 `shouldBe` Just [5] let foo :: MonadReader Int m => m (Int, Int) foo = do x <- local (5+) ask y <- ask return (x, y) runReader (observeT foo) 0 `shouldBe` Just (5, 0) describe "MFunctor instance" $ do it "obeys the hoist identity law" $ hedgehog $ do s <- forAll simpleSeqT hoist (\x -> x) s === s describe "MonadTrans instance" $ do it "obeys the pure/lift law" $ hedgehog $ do a <- forAll (Gen.integral (Range.constant 0 10000)) (lift (pure a) :: SeqT TestM Int) === pure a it "obeys the >>=/lift law" $ hedgehog $ do m <- forAll simpleTestM f :: Int -> TestM Int <- Fun.forAllFn (Fun.fn simpleTestM) (lift m >>= lift . f :: SeqT TestM Int) === lift (m >>= f) describe "msplit" $ do it "obeys msplit empty law" $ L.msplit (empty :: SeqT TestM Int) `shouldBe` pure Nothing it "obeys msplit of cons law" $ hedgehog $ do a <- forAll (Gen.integral (Range.constant 0 10000)) m <- forAll simpleSeqT L.msplit (pure a <|> m) === pure (Just (a, m)) describe "interleave" $ do it "behaves as documented on examples" $ do let x = choose [1,2,3] y = choose [4,5,6] z = choose [7,8,9] :: Seq Int observeAll (x `L.interleave` y) `shouldBe` [1,4,2,5,3,6] observeAll ((x `L.interleave` y) `L.interleave` z) `shouldBe` [1,7,4,8,2,9,5,3,6] observeAll (y `L.interleave` z) `shouldBe` [4,7,5,8,6,9] observeAll (x `L.interleave` (y `L.interleave` z)) `shouldBe` [1,4,2,7,3,5,8,6,9] describe ">>-" $ do it "behaves as documented in class documentation examples" $ do let odds :: Seq Int odds = pure 1 <|> fmap (2 +) odds oddsPlus n = odds >>= \a -> pure (a + n) q = do x <- (pure 0 <|> pure 1) L.>>- oddsPlus if even x then pure x else empty observeMany 3 q `shouldBe` [2,4,6] let m = choose [2,7 :: Int] k x = choose [x, x + 1] h x = choose [x, x * 2] observeAll (m >>= (\x -> k x >>= h)) `shouldBe` [2,4,3,6,7,14,8,16] observeAll ((m >>= k) >>= h) `shouldBe` [2,4,3,6,7,14,8,16] observeAll (m >>- (\x -> k x >>- h)) `shouldBe` [2,7,3,8,4,14,6,16] observeAll ((m >>- k) >>- h) `shouldBe` [2,7,4,3,14,8,6,16] let booyakasha = (pure (0 :: Int) <|> pure 1) >>- oddsPlus >>- \x -> if even x then pure x else empty observeMany 10 booyakasha `shouldBe` [2,4,6,8,10,12,14,16,18,20] describe "once" $ do it "behaves as documented in class documentation example" $ do let divisors n = do a <- choose [2..n-1] b <- choose [2..n-1] guard (a * b == n) pure (a, b) composite v = "Composite" <$ once (divisors v) observeAll (composite 20) `shouldBe` ["Composite"] describe "lnot" $ do it "behaves as documented in class documentation example" $ do let divisors n = do d <- choose [2..n-1] guard (n `rem` d == 0) pure d prime v = do _ <- lnot (divisors v) pure True observeAll (prime 20) `shouldBe` [] observeAll (prime 19) `shouldBe` [True] describe "ifte" $ do it "obeys the law ifte (pure a) th el == th a" $ hedgehog $ do a <- forAll (Gen.integral (Range.constant 0 10000)) th :: Int -> SeqT TestM Int <- Fun.forAllFn (Fun.fn simpleSeqT) let el = error "Should not reach el" ifte (pure a) th el === th a it "obeys the law ifte empty th el == el" $ hedgehog $ do let th = error "Should not reach th" el <- forAll simpleSeqT ifte empty th el === el it "obeys the law ifte (pure a <|> m) th el == th a <|> (m >>= th)" $ hedgehog $ do a <- forAll (Gen.integral (Range.constant 0 10000)) m <- forAll (Gen.small simpleSeqT) th :: Int -> SeqT TestM Int <- Fun.forAllFn (Fun.fn simpleSeqT) let el = error "Should not reach el" (ifte (pure a <|> m) th el) === (th a <|> (m >>= th)) it "behaves as documented in class documentation example" $ do -- Note: at the moment (logict-0.7.1.0) this example is actually -- written wrong. It's corrected below, and will be fixed upstream -- in the next version. let divisors n = do d <- choose [2..n-1] guard (n `rem` d == 0) pure d prime v = once (ifte (divisors v) (const (pure False)) (pure True)) observeAll (prime 20) `shouldBe` [False] observeAll (prime 19) `shouldBe` [True] describe "cons" $ do it "works as documented" $ hedgehog $ do a <- forAll (Gen.integral (Range.constant 0 10000)) s <- forAll simpleSeqT cons a s === (pure a <|> s) describe "consM" $ do it "works as documented" $ hedgehog $ do ma <- forAll simpleTestM s <- forAll simpleSeqT consM ma s === (lift ma <|> s) describe "choose" $ do it "works as documented" $ hedgehog $ do lst <- forAll $ Gen.list (Range.linear 0 10) (Gen.int (Range.constant 0 10000)) choose lst === foldr (\a s -> pure a <|> s) (empty :: SeqT TestM Int) lst it "works on infinite lists" $ observeManyT 4 (choose [1 ..] :: SeqT TestM Integer) `shouldBe` pure [1,2,3,4] describe "chooseM" $ do it "works as documented" $ hedgehog $ do lst <- forAll $ Gen.list (Range.linear 0 5) (Gen.small simpleTestM) chooseM lst === foldr (\ma s -> lift ma <|> s) (empty :: SeqT TestM Int) lst it "works on infinite lists" $ do let lst = cycle [[3,4],[5],[6,7]] :: [[Int]] (shouldBe `on` observeManyT 4) (chooseM lst) (foldr (\ma s -> lift ma <|> s) empty lst) describe "foldMap" $ do it "works like LogicT" $ hedgehog $ do s <- forAll simpleSeqT f :: Int -> [Int] <- Fun.forAllFn (Fun.fn (Gen.list (Range.linear 0 5) (Gen.int (Range.constant 0 10000)))) foldMap f s === foldMap f (Compat.toLogicT s) describe "traverse" $ do it "works like LogicT" $ hedgehog $ do s <- forAll simpleSeq f :: Int -> Identity Int <- (Identity .) <$> Fun.forAllFn (Fun.fn (Gen.int (Range.constant 0 10000))) traverse f s === (Compat.fromLogicT <$> traverse f (Compat.toLogicT s)) -- ------- -- Reimplementation of Control.Monad.Free without the need -- to futz with Data.Functor.Classes. data Free f a = Pure a | Free (f (Free f a)) deriving (Functor, Foldable, Traversable) deriving instance (Show a, Show (f (Free f a))) => Show (Free f a) deriving instance (Read a, Read (f (Free f a))) => Read (Free f a) deriving instance (Eq a, Eq (f (Free f a))) => Eq (Free f a) instance Functor f => Applicative (Free f) where pure = Pure (<*>) = ap instance Functor f => Monad (Free f) where Pure a >>= f = f a Free ffa >>= f = Free $ (>>= f) <$> ffa