How to write a test for StateT using QuickCheck

旧街凉风 提交于 2019-12-10 23:33:41

问题


The StateT is in Control.Monad.Trans.State.Lazy

The function inside and m being higher kinded makes it hard

{-# LANGUAGE FlexibleContexts #-}
import Test.QuickCheck

newtype StateT s m a = StateT { runStateT :: s -> m (a,s) }
instance (CoArbitrary s, Arbitrary s, Arbitrary a) => 
    Arbitrary (StateT s (Maybe) a) where -- doesn't quite work
    arbitrary = undefined

The reason I want to do this is because I want to check using QuickCheck if the applicative instance for StateT that I write (for practice) is correct

Edit: ok, here is the instance I want to test (supposedly incorrect)

instance (Monad m) => Applicative (StateT s m) where
    pure x = StateT (\s -> (\a -> (a, s)) <$> pure x)
    StateT smfs <*> StateT smas = StateT $ \s -> liftA2 (\ (f, s) (a, _) -> (f a, s)) (smfs s) (smas s)

回答1:


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.



来源:https://stackoverflow.com/questions/43409610/how-to-write-a-test-for-statet-using-quickcheck

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!