Skip to content

[TEST] strictness tests for the Strict|Lazy State monad - #132

Open
ynishiza wants to merge 4 commits into
haskell:mainfrom
ynishiza:test/strictness-state
Open

ynishiza wants to merge 4 commits into
haskell:mainfrom
ynishiza:test/strictness-state

Conversation

@ynishiza

@ynishiza ynishiza commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Tests for the Lazy|Strict State transformer.

The approach is analogous to the Writer tests in #127.

  • State functions: evalStateT, execStateT, mapStateT, withStateT, put, modify, modify', modifyM gets
  • Functor/Applicative/Monad: fmap, <*>, liftA2, >>= (same as the Writer tests)
  • Other type classes: Contravariant, MonadFix (same as the Writer tests)
  • lifts: writer lifts liftListen, liftPass, since the behavior differs for these, as it did also in the Writer transformers

@ynishiza
ynishiza force-pushed the test/strictness-state branch from 081bda7 to 94ca230 Compare October 11, 2026 10:22
@ynishiza ynishiza changed the title [TEST] strictness tests for the State monad (Lazy/Strict) [TEST] strictness tests for the Strict|Lazy State monad Oct 11, 2026
@ynishiza
ynishiza force-pushed the test/strictness-state branch from 94ca230 to 83eb097 Compare October 11, 2026 10:52
Comment thread test/Arbitrary.hs
Comment on lines 45 to +46
data BaseMonad
= forall m. (Typeable m, Monad m) => LazyBaseMonad (forall a. m a -> IO a)
| forall m. (Typeable m, Monad m) => StrictBaseMonad (forall a. m a -> IO a)
= forall m. (Typeable m, Monad m) => BaseMonad (forall a. m a -> IO a)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I simplified this to just BaseMonad since there are different nuances of strictness which can get complicated to categorize.

Instead, if there is a difference in the behavior depending on the underlying monad, I opted to just check the actual monad type directly and explain with a comment, as done in one of the tests here.
Please let me know if you have any thoughts.

Comment thread test/StrictnessCheck.hs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just the strictness assertions moved out of the writer tests

Comment thread test/StateStrictness.hs
@ynishiza
ynishiza force-pushed the test/strictness-state branch from 83eb097 to 1781938 Compare October 11, 2026 10:56
@ynishiza
ynishiza marked this pull request as ready for review October 11, 2026 10:58
@L0neGamer
L0neGamer self-requested a review October 16, 2026 15:33

@L0neGamer L0neGamer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty darn good! Very happy with what you've done here.

Comment thread test/main.hs
main = do
ComposeT.test
WriterStrictness.test
defaultMain $ testGroup "Transformers" [

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly "transformers strictness"

Comment thread test/StateStrictness.hs
Comment on lines +37 to +53
testGroup "evalStateT" [
testProperty "Lazy" $ \(F1Bot (x :: () -> (Int, ()))) ->
let result = flip Lazy.evalStateT () $ Lazy.StateT (MkSolo . x)
in isLazy result .&. isStrictIn (x ()) (getSolo result),
testProperty "Strict" $ \(F1Bot (x :: () -> (Int, ()))) ->
let result = flip Strict.evalStateT () $ Strict.StateT (MkSolo . x)
in isLazy result .&. isStrictIn (x ()) (getSolo result)
],
testGroup "execStateT" [
-- NOTE: See note on evalStateT.
testProperty "Lazy" $ \(F1Bot (x :: () -> (Int, ()))) ->
let result = flip Lazy.execStateT () $ Lazy.StateT (MkSolo . x)
in isLazy result .&. isStrictIn (x ()) (getSolo result),
testProperty "Strict" $ \(F1Bot (x :: () -> (Int, ()))) ->
let result = flip Strict.execStateT () $ Strict.StateT (MkSolo . x)
in isLazy result .&. isStrictIn (x ()) (getSolo result)
],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with all of these tests, does the underlying monad type not change anything?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for asking, and that's always a difficult question..

I think at least one basic case is the strict data type which will almost always have slightly different behavior:

ghci> import qualified Control.Monad.Trans.State.Lazy as L
ghci> data A a = A !a deriving (Functor, Show)
ghci> data B a = B a deriving (Functor, Show)
ghci> L.execStateT (L.StateT @()  (\_ -> A undefined)) () `seq` ()
*** Exception: Prelude.undefined
CallStack (from HasCallStack):
  undefined, called at <interactive>:63:38 in interactive:Ghci21
ghci> L.execStateT (L.StateT @()  (\_ -> B undefined)) () `seq` ()
()

It's a bit similar to the difference that was discussed here: #123 (comment)

Or did you have something else in mind..?

Ideally, we would like to be able to test for all possible cases and each of their behaviors, but comes at the cost of greatly complicating the tests... which is why I've so far opted on using Solo as described in the Writer test documentation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine

@L0neGamer

Copy link
Copy Markdown
Collaborator

I'll try give another review over the week. I couldn't see much wrong so it'll likely be an approval!

@L0neGamer L0neGamer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no more I can say about this test. thank you for it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants