Your question is really interesting. Indeed, it would be very nice to verify functor/apllicative/monad laws using QuickCheck
for StateT
monad transformer. Because this is one of the most useful applications of QuickCheck
.
But writing Arbitrary
instance for StateT
is not trivial. It's possible. But actually there is no profit in it. You should use CoArbitrary
type class in some smart way. And also couple extensions. The idea is described in this blog post. Having CoArbitrary
instance for a -> b
you can easily create Arbitrary
instance for StateT
.
instance ( CoArbitrary s
, Arbitrary s
, Arbitrary a
, Arbitrary (m a)
, Monad m
) => Arbitrary (StateT s m a)
where
arbitrary = StateT <$> promote (\s -> fmap (,s) <$> arbitrary)
Then you can generate states:
ghci> (`runStateT` 3) <$> generate (arbitrary @(StateT Int Maybe Bool))
Just (True,3)
And you can even write property:
propStateFunctorId :: forall m s a .
( Arbitrary s
, Eq (m (a, s))
, Show s
, Show (m (a, s))
, Functor m
)
=> StateT s m a -> Property
propStateFunctorId st = forAll arbitrary $ \s ->
runStateT (fmap id st) s === runStateT st s
But you can't run this property because it requires instance Show
for StateT
and you can't write sensible instance for it :( It's just how QuickCheck
works. It should print failed counterexample. Knowledge of the fact that 100 tests were passed and 1 test failed doesn't really help you if you don't know which one failed. This is not a programming contest :)
ghci> quickCheck (propStateFunctorId @Maybe @Int @Bool)
<interactive>:68:1: error:
• No instance for (Show (StateT Int Maybe Bool))
arising from a use of ‘quickCheck’
So instead of generating arbitrary StateT
it's better to generate s
and a
and then check properties.
prop_StateTFunctorId :: forall s a .
( Arbitrary s
, Arbitrary a
, Eq a
, Eq s
)
=> s -> a -> Bool
prop_StateTFunctorId s a = let st = pure a
in runStateT @_ @Maybe (fmap id st) s == runStateT st s
ghci> quickCheck (prop_StateTFunctorId @Int @Bool)
+++ OK, passed 100 tests.
This approach doesn't require knowledge of some high-level skills.
StateT
it's not inbase
). I'm talking about that you ask about "X" (how to write an Arbitarry instance) but actually just want "Y" (how to test my applicative instance of StateT). – Zeta