2017-04-14 70 views
2

编写StateT测试的StateT是Control.Monad.Trans.State.Lazy如何使用快速检查

内部的功能和m是更高kinded使得它很难

{-# 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 

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

编辑: 好的,这里是我想测试的实例(假设不正确)

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) 
+0

......听起来像是一个XY问题。 – Zeta

+0

@Zeta我认为每个人都可以搜索解决方案,因为StateT在基础库中......但重点是,我想测试它 – pterodragon

+0

我不是在谈论*“重新发明车轮”*(由方式,'StateT'不在'base'中)。我在谈论你[询问“X”(如何写一个Arbitarry实例),但实际上只是想要“Y”(如何测试我的应用程序的StateT实例)](https://meta.stackexchange.com/问题/ 66377 /什么,是最XY-问题)。 – Zeta

回答

2

你的问题真的很有趣。事实上,使用QuickCheck来验证函数/定律/单子定律对于单子变压器StateT将是非常好的。因为这是QuickCheck最有用的应用之一。

但是写Arbitrary实例为StateT并不重要。这是可能的。但实际上它没有利润。您应该以某种智能方式使用CoArbitrary类型类。还有几个扩展。该想法在this blog post中描述。对于a -> b,有CoArbitrary实例,您可以轻松地为StateT创建Arbitrary实例。

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) 

然后你就可以生成状态:

ghci> (`runStateT` 3) <$> generate (arbitrary @(StateT Int Maybe Bool)) 
Just (True,3) 

你甚至可以写属性:

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 

但由于它需要instance ShowStateT,你不能运行这个属性不能写出明智的实例:(这只是QuickCheck的工作原理,它应该打印失败的反例,了解100 te如果你不知道哪一个失败了,那么sts通过并且一次测试失败并不能真正帮助你。这不是一个编程大赛:)

ghci> quickCheck (propStateFunctorId @Maybe @Int @Bool) 
<interactive>:68:1: error: 
    • No instance for (Show (StateT Int Maybe Bool)) 
     arising from a use of ‘quickCheck’ 

因此,而不是生成任意StateT最好是产生sa,然后检查性能。

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. 

这种方法不需要一些高级技能的知识。

+0

:)我可以通过使用任意:: Gen(StateT Int Maybe Bool)来生成StateT实例。在生成StateT实例和'prop_StateTFunctorId'时,我实际上并不完全使用“@”符号。这是否是某种语言扩展?我无法搜索它... – pterodragon

+2

@pterodragon是的,它是'-XTypeApplications'扩展名。你可以在我的答案中找到一些信息:http://stackoverflow.com/questions/42999199/how-can-i-get-the-type-of-a-polymorphic-function-for-a-specific-type-class -insta/43000393#43000393有链接到haskell用户指南,你可以阅读更多。非常有用和很好的功能。这里简要概述:http://kseo.github.io/posts/2017-01-08-visible-type-application-ghc8.html – Shersh

+0

我的机器没有ghc 8,我可能会稍后检查:) – pterodragon