如何使用QuickCheck为StateT编写测试 [英] How to write a test for StateT using QuickCheck

查看:140
本文介绍了如何使用QuickCheck为StateT编写测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

StateT位于 Control.Monad.Trans.State.Lazy



里面的函数和 m 变得越来越困难

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

newtype StateT sma = StateT {runStateT :: s - > m(a,s)}
实例(CoArbrary s,Arbitrary s,Arbitrary a)=>
Arbitrary(StateT s(Maybe)a)where - does not work
arbitrary = undefined

我想这样做的原因是因为我想检查使用QuickCheck,如果我编写的StateT的应用实例(用于练习)是正确的

<编辑:
好​​的,这里是我想测试的实例(应该是不正确的)

  instance(Monad m )=>应用(StateT sm)其中
pure x = StateT(\ s - >(\ a - >(a,s))< $>纯x)
StateT smfs< * GT; StateT smas = StateT $ \s - > liftA2(\(f,s)(a,_) - >(fa,s))(smfs s)(smas s)


解决方案

你的问题真的很有趣。事实上,使用 QuickCheck 来验证 StateT monad变换器将是非常好的验证函子/ apllicative / monad法则。因为这是 QuickCheck 最有用的应用程序之一。

但是为 StateT 写入任意实例并不重要。这是可能的。但实际上它没有利润。你应该以一些聪明的方式使用 CoArbrary 类型类。还有几个扩展。这个想法在中描述博客文章 a - >有 CoArbitrary 实例。 b 您可以轻松为 StateT 创建任意实例。

 实例(CoArbitrary s 
,任意s
,任意a
,任意(ma)
,Monad m
)=>任意(StateT s m a)
其中
任意= StateT< $> (\ s - > fmap(,s)< $>任意)

然后你可以生成状态:

  ghci> (`runStateT` 3)< $>生成(任意@(StateT Int Maybe Bool))
只是(真,3)

你甚至可以编写属性:

  propStateFunctorId :: forall msa。 
(任意s
,Eq(m(a,s))
,显示s
,显示(m(a,s))
,函子m

=> StateT s m a - >属性
propStateFunctorId st = forAll任意$ \s - >
runStateT(fmap id st)s === runStateT st

但是你可以' t运行这个属性,因为它需要 instance为 StateT 显示,并且你不能为它写出明智的实例:(这是只要 QuickCheck 有效,它应该打印失败的反例。知道100个测试通过了,1个测试失败的事实并不能真正帮助你,如果你不知道哪个这不是一场编程竞赛:)

  ghci> quickCheck(propStateFunctorId @Maybe @Int @Bool)
< interactive>:68:1:error:
•没有用于(Show(StateT Int Maybe Bool))
的实例'quickCheck'

因此,不要生成任意 StateT 最好生成 s a ,然后检查属性。

  prop_StateTFunctorId :: forall sa。 
(任意s
,任意a
,等式a
,等式

=> s - > a - > Bool
prop_StateTFunctorId s a = let st = pure a
在runStateT @_ @Maybe(fmap id st)s == runStateT st

ghci> quickCheck(prop_StateTFunctorId @Int @Bool)
+++ OK,通过了100次测试。

这种方法并不需要一些高级技能。


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)

解决方案

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.

这篇关于如何使用QuickCheck为StateT编写测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆