1+ {-# LANGUAGE FlexibleInstances #-}
12{-# LANGUAGE PolyKinds #-}
23{-# LANGUAGE RankNTypes #-}
34{-# LANGUAGE RecordWildCards #-}
45{-# LANGUAGE ScopedTypeVariables #-}
56{-# LANGUAGE TypeApplications #-}
7+ {-# LANGUAGE UndecidableInstances #-}
68
79module WriterStrictness (test ) where
810
11+ import Arbitrary
12+
913import Control.Applicative
1014import Control.Arrow (first )
1115import Control.Exception (ErrorCall (.. ), SomeException (.. ),
1216 evaluate )
17+ import Control.Exception.Base (displayException )
1318import Control.Monad
1419import Control.Monad.Fix
1520import Control.Monad.IO.Class
@@ -27,6 +32,7 @@ import Data.Coerce
2732import Data.Either (fromRight )
2833import Data.Functor.Contravariant
2934import Data.Functor.Identity
35+ import Data.List (isPrefixOf )
3036import Data.Maybe
3137import Data.Monoid
3238
@@ -38,10 +44,13 @@ import Test.QuickCheck.Monadic (assertExceptionIO, monadicIO, run)
3844import Test.Tasty
3945import Test.Tasty.QuickCheck
4046
41- import Arbitrary
42-
4347test :: IO ()
44- test = defaultMain $ testGroup " Writer" strictPairTests
48+ test = defaultMain $ testGroup " Writer"
49+ [
50+ testGroup " Pair (a, w)" strictPairTests2,
51+ testGroup " Log w" strictLogTest,
52+ testGroup " Pair OLD" strictPairTests
53+ ]
4554
4655data WriterBase writer
4756 = WriterBase
@@ -134,6 +143,126 @@ cpsWriterLifts =
134143 { liftCatch = CPS. liftCatch
135144 }
136145
146+
147+ data WriterProp a = WriterProp
148+ { lazy :: Prop a ,
149+ strict :: Prop a ,
150+ cps :: Prop a
151+ }
152+
153+ data Prop a = Prop a | Unsupported
154+
155+ runWriterProps :: Testable a => String -> WriterProp a -> TestTree
156+ runWriterProps name WriterProp {.. } =
157+ testGroup name $ catMaybes [
158+ runProp " Lazy" lazy,
159+ runProp " Strict" strict,
160+ runProp " CPS" cps
161+ ]
162+ where
163+ runProp propName (Prop a) = Just $ testProperty propName a
164+ runProp _ Unsupported = Nothing
165+
166+ strictPairTests2 :: [TestTree ]
167+ strictPairTests2 = [
168+ runWriterProps " execWriter" WriterProp {
169+ lazy = Prop $ \ (m :: BaseMonad , Bot (v :: (Int , String ))) -> isStrict v $ withBaseMonad m $ Lazy. execWriterT $ Lazy. writer v,
170+ strict = Prop $ \ (m :: BaseMonad , Bot (v :: (Int , String ))) -> isStrict v $ withBaseMonad m $ Strict. execWriterT $ Strict. writer v,
171+ cps = Prop $ \ (m :: BaseMonad , Bot (v :: (Int , String ))) -> isStrict v $ withBaseMonad m $ CPS. execWriterT $ CPS. writer v
172+ },
173+ runWriterProps " mapWriter" WriterProp {
174+ lazy = Prop $ \ (m :: BaseMonad , Bot (v :: (Int , String ))) -> isStrict v $ withBaseMonad m $ Lazy. runWriterT $ Lazy. mapWriterT id $ Lazy. writer v,
175+ strict = Prop $ \ (m :: BaseMonad , Bot (v :: (Int , String ))) -> isStrict v $ withBaseMonad m $ Strict. runWriterT $ Strict. mapWriterT id $ Strict. writer v,
176+ cps = Prop $ \ (m :: BaseMonad , Bot (v :: (Int , String ))) -> isStrict v $ withBaseMonad m $ CPS. runWriterT $ CPS. mapWriterT id $ CPS. writer v
177+ },
178+ runWriterProps " listen" WriterProp {
179+ lazy = Prop $ \ (m :: BaseMonad , Bot (v :: (Float , Sum Int ))) -> isLazy $ withBaseMonad m $ Lazy. runWriterT $ Lazy. listen $ Lazy. writer v,
180+ strict = Prop $ \ (m :: BaseMonad , Bot (v :: (Float , Sum Int ))) -> isStrict v $ withBaseMonad m $ Strict. runWriterT $ Strict. listen $ Strict. writer v,
181+ cps = Prop $ \ (m :: BaseMonad , Bot (v :: (Float , Sum Int ))) -> isStrict v $ withBaseMonad m $ CPS. runWriterT $ CPS. listen $ CPS. writer v
182+ },
183+ runWriterProps " pass" WriterProp {
184+ lazy = Prop $ \ (m :: BaseMonad , Bot (v :: (() , F1 [Int ] [Int ]))) ->
185+ let v' = coerce v :: (() , [Int ] -> [Int ])
186+ in isLazy $ withBaseMonad m $ Lazy. runWriterT $ Lazy. pass $ Lazy. writer (v', [] ),
187+ strict = Prop $ \ (m :: BaseMonad , Bot (v :: (() , F1 [Int ] [Int ]))) ->
188+ let v' = coerce v :: (() , [Int ] -> [Int ])
189+ in isStrict v $ withBaseMonad m $ Strict. runWriterT $ Strict. pass $ Strict. writer (v', [] ),
190+ cps = Prop $ \ (m :: BaseMonad , Bot (v :: (() , F1 [Int ] [Int ]))) ->
191+ let v' = coerce v :: (() , [Int ] -> [Int ])
192+ in isStrict v $ withBaseMonad m $ CPS. runWriterT $ CPS. pass $ CPS. writer (v', [] )
193+ },
194+ runWriterProps " censor" WriterProp {
195+ lazy = Prop $ \ (m :: BaseMonad , Bot (v :: (() , [Int ]))) -> isLazy $ withBaseMonad m $ Lazy. runWriterT $ Lazy. censor id $ Lazy. writer v,
196+ strict = Prop $ \ (m :: BaseMonad , Bot (v :: (() , [Int ]))) -> isStrict v $ withBaseMonad m $ Strict. runWriterT $ Strict. censor id $ Strict. writer v,
197+ cps = Prop $ \ (m :: BaseMonad , Bot (v :: (() , [Int ]))) -> isStrict v $ withBaseMonad m $ CPS. runWriterT $ CPS. censor id $ CPS. writer v
198+ },
199+
200+ -- == Functor/Applicative/Monad ==
201+ runWriterProps " Monad: >>=" WriterProp {
202+ lazy = Prop $ \ (m :: BaseMonad , Bot v :: (Bot (() , String )), Bot w :: (Bot (Int , String ))) ->
203+ isLazy $ withBaseMonad m $ Lazy. runWriterT $ Lazy. writer v >>= const (Lazy. writer w),
204+ strict = Prop $ \ (m :: BaseMonad , Bot v :: (Bot (() , String )), Bot w :: (Bot (Int , String ))) ->
205+ isStrict2 v w $ withBaseMonad m $ Strict. runWriterT $ Strict. writer v >>= const (Strict. writer w),
206+ cps = Prop $ \ (m :: BaseMonad , Bot v :: (Bot (() , String )), Bot w :: (Bot (Int , String ))) ->
207+ isStrict2 v w $ withBaseMonad m $ CPS. runWriterT $ CPS. writer v >>= const (CPS. writer w)
208+ },
209+ runWriterProps " Alternative: <|>" WriterProp {
210+ lazy = Prop $ \ (v :: [Bot (Int , String )], w :: [Bot (Int , String )]) ->
211+ let v' = coerce v :: [(Int , String )]
212+ w' = coerce w :: [(Int , String )]
213+ in isBottom (Lazy. runWriterT (Lazy. WriterT v' <|> Lazy. WriterT w')) === False ,
214+ strict = Prop $ \ (v :: [Bot (Int , String )], w :: [Bot (Int , String )]) ->
215+ let v' = coerce v :: [(Int , String )]
216+ w' = coerce w :: [(Int , String )]
217+ in any isBottom (Strict. runWriterT (Strict. WriterT v' <|> Strict. WriterT w')) === (any isBottom v' || any isBottom w'),
218+ cps = Prop $ \ (v :: [Bot (Int , String )], w :: [Bot (Int , String )]) ->
219+ let v' = coerce v :: [(Int , String )]
220+ w' = coerce w :: [(Int , String )]
221+ in any isBottom (CPS. runWriterT (CPS. writerT v' <|> CPS. writerT w')) === (any isBottom v' || any isBottom w')
222+ },
223+
224+ -- == Other type classes ==
225+ runWriterProps " Foldable: foldMap" WriterProp {
226+ -- NOTE: foldMap is lazy in both Lazy and Strict Writers due to the use of fst to pick the value.
227+ lazy = Prop $ \ (v :: [Bot (Int , String )]) ->
228+ let v' = coerce v :: [(Int , String )] in isLazyValue $ getSum $ foldMap (const (Sum (0 :: Int ))) (Lazy. WriterT v' ),
229+ strict = Prop $ \ (v :: [Bot (Int , String )]) ->
230+ let v' = coerce v :: [(Int , String )] in isLazyValue $ getSum $ foldMap (const (Sum (0 :: Int ))) (Strict. WriterT v' ),
231+ cps = Unsupported
232+ },
233+ runWriterProps " contramap" WriterProp {
234+ lazy = Prop $ \ (Bot (v :: (Int , String ))) ->
235+ let f = getOp $ Lazy. runWriterT $ contramap (+ 1 ) $ Lazy. WriterT (Op id ) in isLazyValue $ f v,
236+ strict = Prop $ \ (Bot (v :: (Int , String ))) ->
237+ let f = getOp $ Strict. runWriterT $ contramap (+ 1 ) $ Strict. WriterT (Op id ) in isStrictValue v $ f v,
238+ cps = Unsupported
239+ }
240+ ]
241+
242+ strictLogTest :: [TestTree ]
243+ strictLogTest = [
244+ runWriterProps " runWriter" WriterProp {
245+ lazy = Prop $ \ (m :: BaseMonad , Bot (v :: Sum Int )) -> isLazy (withBaseMonad m $ Lazy. runWriterT $ Lazy. writer (() , v)),
246+ strict = Prop $ \ (m :: BaseMonad , Bot (v :: Sum Int )) -> isLazy (withBaseMonad m $ Strict. runWriterT $ Strict. writer (() , v)),
247+ cps = Prop $ \ (m :: BaseMonad , Bot (v :: Sum Int )) -> isStrict v (withBaseMonad m $ CPS. runWriterT $ CPS. writer (() , v))
248+ },
249+ runWriterProps " mapWriter" WriterProp {
250+ lazy = Prop $ \ (m :: BaseMonad , Bot (v :: String )) -> isLazy (withBaseMonad m $ Lazy. runWriterT $ Lazy. mapWriterT id $ Lazy. writer (0 :: Int , v )),
251+ strict = Prop $ \ (m :: BaseMonad , Bot (v :: String )) -> isLazy (withBaseMonad m $ Strict. runWriterT $ Strict. mapWriterT id $ Strict. writer(0 :: Int , v )),
252+ cps = Prop $ \ (m :: BaseMonad , Bot (v :: String )) -> isStrict v (withBaseMonad m $ CPS. runWriterT $ CPS. mapWriterT id $ CPS. writer (0 :: Int , v ))
253+ },
254+ runWriterProps " listen" WriterProp {
255+ lazy = Prop $ \ (m :: BaseMonad , Bot (v :: Sum Int )) -> isLazy (withBaseMonad m $ Lazy. runWriterT $ Lazy. listen $ Lazy. writer (() , v)),
256+ strict = Prop $ \ (m :: BaseMonad , Bot (v :: Sum Int )) -> isLazy (withBaseMonad m $ Strict. runWriterT $ Strict. listen $ Strict. writer (() , v)),
257+ cps = Prop $ \ (m :: BaseMonad , Bot (v :: Sum Int )) -> isStrict v (withBaseMonad m $ CPS. runWriterT $ CPS. listen $ CPS. writer (() , v))
258+ },
259+ runWriterProps " tell" WriterProp {
260+ lazy = Prop $ \ (m :: BaseMonad , Bot (v :: String )) -> isLazy (withBaseMonad m $ Lazy. runWriterT $ Lazy. tell v),
261+ strict = Prop $ \ (m :: BaseMonad , Bot (v :: String )) -> isLazy (withBaseMonad m $ Strict. runWriterT $ Strict. tell v),
262+ cps = Prop $ \ (m :: BaseMonad , Bot (v :: String )) -> isStrict v (withBaseMonad m $ CPS. runWriterT $ CPS. tell v)
263+ }
264+ ]
265+
137266-- | Strictness tests for the value (a, w) of Writers
138267--
139268-- Test whether the pair (a, w) is handled lazily/strictly
@@ -641,7 +770,7 @@ runTestMonadStack m cont = runReaderT rd 0
641770const2 :: a -> b -> c -> a
642771const2 = const . const
643772
644- test_pair_strictness_monad :: forall k writer w ( m :: k ) a b . (writer w m a -> b ) -> Bool -> writer w m a -> Property
773+ test_pair_strictness_monad :: (writer w m a -> b ) -> Bool -> writer w m a -> Property
645774test_pair_strictness_monad runWriter expected writer = test_pair_strictness_IOBase expected $ evaluate (runWriter writer)
646775
647776test_pair_strictness_IO :: (writer w IO a -> IO b ) -> Bool -> writer w IO a -> Property
@@ -652,3 +781,32 @@ test_pair_strictness_IOBase True v = assertExceptionIO @ErrorCall (const True) v
652781test_pair_strictness_IOBase False v = monadicIO $ run $ do
653782 v' <- v
654783 v' `seq` return ()
784+
785+ isStrictValue :: arg1 -> a -> Property
786+ isStrictValue x = isStrict x . evaluate
787+
788+ isLazyValue :: a -> Property
789+ isLazyValue = isLazy . evaluate
790+
791+ -- Strictness in one argument:
792+ -- The result (normalized to IO) should be bottom whenever arg1 is bottom.
793+ isStrict :: arg1 -> IO a -> Property
794+ isStrict x = shouldBeBottom (isBottom x)
795+
796+ -- Strictness in two arguments:
797+ -- The result (normalized to IO) should be bottom whenever arg1 OR arg2 is bottom.
798+ isStrict2 :: arg1 -> arg2 -> IO a -> Property
799+ isStrict2 x y = shouldBeBottom (isBottom x || isBottom y)
800+
801+ isLazy :: IO a -> Property
802+ isLazy = shouldBeBottom False
803+
804+ shouldBeBottom :: Bool -> IO a -> Property
805+ shouldBeBottom True result = assertExceptionIO isBottomError result
806+ where
807+ -- Check error message only i.e. prefix, ignoring stacktrace.
808+ isBottomError :: ErrorCall -> Bool
809+ isBottomError e = " <bottom>" `isPrefixOf` displayException e
810+ shouldBeBottom False result = monadicIO $ run $ do
811+ v <- result
812+ v `seq` return ()
0 commit comments