@@ -26,10 +26,14 @@ import Test.Tasty.QuickCheck
2626test :: TestTree
2727test = testGroup " State" strictnessTest
2828
29+ -- | NOTE: Strictness test
30+ --
31+ -- See note in WriterStrictness.hs regarding the choice of Solo as the monad used for testing.
32+ --
2933strictnessTest :: [TestTree ]
3034strictnessTest = [
31- -- NOTE: Lazy and Strict are the same since there is no computational sequence involved.
32- -- The output is exactly the same as the input.
35+ -- NOTE: Lazy and Strict are the same since there is no computation involved.
36+ -- i.e. just returns the input.
3337 testGroup " evalStateT" [
3438 testProperty " Lazy" $ \ (F1Bot (x :: () -> (Int , () ))) ->
3539 let result = flip Lazy. evalStateT () $ Lazy. StateT (MkSolo . x)
@@ -49,8 +53,7 @@ strictnessTest = [
4953 ],
5054
5155 testGroup " withStateT" [
52- -- NOTE: Lazy and Strict are the same since there is no computational sequence involved.
53- -- The output is exactly the same as the input.
56+ -- NOTE: f is not evaluated strictly, so the result depends only on the original input.
5457 testProperty " Lazy" $ \ (F1Bot (f :: () -> () )) (F1Bot (x :: () -> (Int , () ))) ->
5558 let result = flip Lazy. runStateT () $ Lazy. withStateT f (Lazy. StateT (MkSolo . x))
5659 in isLazy result .&. isStrictIn (x () ) (getSolo result),
@@ -59,14 +62,28 @@ strictnessTest = [
5962 in isLazy result .&. isStrictIn (x () ) (getSolo result)
6063 ],
6164
65+ testGroup " mapStateT" [
66+ -- NOTE: f is applied directly on the underlying monad without any intervention by the State, so the result
67+ -- depends only on the result of f.
68+ testProperty " Lazy" $ \ (F1Bot (f :: (Int , () ) -> (Int , () ))) ->
69+ let result = flip Lazy. runStateT () $ Lazy. mapStateT (f <$> ) (Lazy. StateT (MkSolo . const (0 , () )))
70+ in isLazy result .&. isStrictIn (f (0 , () )) (getSolo result),
71+ testProperty " Strict" $ \ (F1Bot (f :: (Int , () ) -> (Int , () )) ) ->
72+ let result = flip Strict. runStateT () $ Strict. mapStateT (f <$> ) (Strict. StateT (MkSolo . const (0 , () )))
73+ in isLazy result .&. isStrictIn (f (0 , () )) (getSolo result)
74+ ],
75+
6276 testGroup " put" [
63- -- NOTE: Lazy and Strict are the same since there is no computational sequence involved.
77+ -- NOTE: lazy since there is no computation involved.
6478 testProperty " Lazy" $ \ (Bot (x :: Int )) ->
6579 isValueLazy getSolo $ flip Lazy. runStateT 0 $ Lazy. put x,
6680 testProperty " Strict" $ \ (Bot (x :: Int )) ->
67- isValueLazy getSolo $ flip Lazy . runStateT 0 $ Lazy . put x
81+ isValueLazy getSolo $ flip Strict . runStateT 0 $ Strict . put x
6882 ],
6983
84+ -- NOTE: modify and get variants, with the exception of modify',
85+ -- only act on the state s of the State's context (a, s).
86+ -- Hence, the mapping is evaluated lazily.
7087 testGroup " modify" [
7188 testProperty " Lazy" $ \ (F1Bot (f :: Int -> Int )) ->
7289 isValueLazy getSolo $ flip Lazy. runStateT 0 $ Lazy. modify f,
@@ -85,14 +102,20 @@ strictnessTest = [
85102 testProperty " Strict" $ \ (F1Bot (f :: Int -> Int )) ->
86103 isValueLazy getSolo $ flip Strict. runStateT 0 $ Strict. modifyM $ MkSolo . f
87104 ],
88-
105+ testGroup " gets" [
106+ testProperty " Lazy" $ \ (F1Bot (f :: Int -> () )) ->
107+ isValueLazy getSolo $ flip Lazy. runStateT 0 $ Lazy. gets f,
108+ testProperty " Strict" $ \ (F1Bot (f :: Int -> () )) ->
109+ isValueLazy getSolo $ flip Strict. runStateT 0 $ Strict. gets f
110+ ],
89111
90112 -- == Functor/Applicative/Monad ==
91113 testGroup " Functor: fmap" [
92114 testProperty " Lazy" $ \ (F1Bot (x :: () -> (Int , () ))) ->
93115 isValueLazy getSolo $ flip Lazy. runStateT () $ (+ 1 ) <$> Lazy. StateT (MkSolo . x),
94- testProperty " Strict" $ \ (F1Bot (x :: () -> (Int , () ))) ->
116+ testProperty " Strict" $ \ (F1Bot (x :: () -> (Int , () ))) ->
95117 let result = flip Strict. runStateT () $ (+ 1 ) <$> Strict. StateT (MkSolo . x)
118+ -- Does not bottom in the outer constructor since Functor only exposes control over the inner value.
96119 in isLazy result .&. isStrictIn (x () ) (getSolo result)
97120 ],
98121
@@ -102,18 +125,17 @@ strictnessTest = [
102125 in isValueLazy getSolo $ flip Lazy. runStateT () $ Lazy. StateT (MkSolo . f') <*> Lazy. StateT (MkSolo . x),
103126 testProperty " Strict" $ \ (F1Bot (x :: () -> (Int , () ))) (F1Bot (y :: () -> (F1 Int Int , () ))) ->
104127 let f' = coerce y :: () -> (Int -> Int , () )
105- result = flip Strict. runStateT () $ Strict. StateT (MkSolo . f') <*> Strict. StateT (MkSolo . x)
106- in isBiStrictIn (x () ) (f' () ) (getSolo result)
128+ result = flip Strict. runStateT () $ Strict. StateT (MkSolo . f') <*> Strict. StateT (MkSolo . x)
129+ in isBiStrictIn (x () ) (f' () ) result
107130 ],
108131 testGroup " Applicative: liftA2" [
109132 testProperty " Lazy" $ \ (F1Bot (x :: () -> (Int , () ))) (F1Bot (y :: () -> (Int , () ))) ->
110- isValueLazy getSolo $ Lazy. runStateT (liftA2 (+) (Lazy. StateT $ MkSolo . x) (Lazy. StateT $ MkSolo . y)) () ,
133+ isValueLazy getSolo $ Lazy. runStateT (liftA2 (+) (Lazy. StateT $ MkSolo . x) (Lazy. StateT $ MkSolo . y)) () ,
111134 testProperty " Strict" $ \ (F1Bot (x :: () -> (Int , () ))) (F1Bot (q :: () -> (Int , () ))) ->
112- isBiStrictIn (x () ) (q () ) $ Strict. runStateT (liftA2 (+) (Strict. StateT $ MkSolo . x) (Strict. StateT $ MkSolo . q)) ()
135+ isBiStrictIn (x () ) (q () ) $ Strict. runStateT (liftA2 (+) (Strict. StateT $ MkSolo . x) (Strict. StateT $ MkSolo . q)) ()
113136 ],
114137
115138 testGroup " Monad: >>=" [
116- -- NOTE:
117139 testProperty " Lazy" $ \ (F1Bot (x :: () -> (Int , () ))) (F1Bot (k :: () -> (Int , () ))) ->
118140 let result = flip Lazy. runStateT () $ Lazy. StateT (MkSolo . x) >>= const (Lazy. StateT (MkSolo . k))
119141 in isLazy result .&. isStrictIn (k () ) (getSolo result),
@@ -143,6 +165,7 @@ strictnessTest = [
143165 in x' () === runIdentity (Strict. runStateT (mfix (const (Strict. StateT $ Identity . x'))) () )
144166 ],
145167
168+
146169 -- == Lift ==
147170 testGroup " liftListen" [
148171 testProperty " Lazy" $ \ (F1Bot (x :: () -> (Int , () ))) ->
@@ -177,34 +200,40 @@ strictnessTest = [
177200 (F1Bot (x :: Int -> (String , Int )))
178201 (F1Bot (f :: Int -> Int ))
179202 (Bot (s :: String ))
180- (Bot (t :: () )) ->
181- -- TODO: Solo is strict in sequencing whereas Identity is lazy
203+ (Bot (t :: Int )) ->
204+ -- In general, the computations are lazy since this is the Lazy State.
182205 --
183- -- MkSolo x >>= k = k x strict due to MkSolo
184- -- x >>= k = k (runIdentity x) lazy due to runIdentity
206+ -- However, one exception is modify', which enforces strictness on the state value.
207+ -- Thus, modify' will bottom for monads which are strict in the underlying sequencing.
208+ -- i.e. Solo and IO
185209 --
186- let expected = isStrictMonad m && isBottom (f (snd (x 0 )))
210+ -- In particular, note that Identity is lazy whereas Solo is strict:
211+ --
212+ -- m >>= k = k (runIdentity m) lazy runIdentity
213+ -- MkSolo x >>= k = k x strict pattern match
214+ --
215+ let expected = (baseMonadName m == " Solo" || baseMonadName m == " IO" ) && isBottom (f (snd (x 0 )))
187216 in shouldBeBottomIO expected $ withBaseMonad m $ flip Lazy. runStateT 0 $ do
188217 p <- Lazy. StateT $ return . x
189218 q <- Lazy. get
190- Lazy. modify' f
219+
220+ Lazy. modify' f -- may throw depending on the underlying monad. See above.
221+
191222 Lazy. put $ q + length (s <> p)
192- Lazy. gets (const t)
193- ,
223+ Lazy. gets (+ t),
194224
195225 testProperty " Strict" $
196226 \ m
197227 (F1Bot (x :: Int -> (String , Int )))
198228 (F1Bot (f :: Int -> Int ))
199229 (Bot (s :: String ))
200- (Bot (t :: () )) ->
230+ (Bot (t :: Int )) ->
201231 let expected = isBottom (x 0 ) || isBottom (f 0 )
202232 in shouldBeBottomIO expected $ withBaseMonad m $ flip Strict. runStateT 0 $ do
203233 p <- Strict. StateT $ return . x
204234 q <- Strict. get
205235 Strict. modify' f
206236 Strict. put $ q + length (s <> p)
207- Strict. gets (const t)
237+ Strict. gets (+ t)
208238 ]
209239 ]
210-
0 commit comments