如何使用 QuickCheck 为 StateT 编写测试
How to write a test for StateT using QuickCheck
StateT 在Control.Monad.Trans.State.Lazy
里面的功能和m
更高级的东西很难
{-# 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)
你的问题很有意思。确实,使用 QuickCheck
来验证 functor/apllicative/monad 法则对于 StateT
monad 转换器来说会非常好。因为这是 QuickCheck
最有用的应用之一。
但是为 StateT
编写 Arbitrary
实例并不简单。这是可能的。但实际上并没有任何利润。您应该以某种巧妙的方式使用 CoArbitrary
类型 class。还有一些扩展。 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 Show
用于 StateT
而你不能为它编写合理的实例 :( 这就是如何QuickCheck
有效。它应该打印失败的反例。如果您不知道哪个测试失败,那么知道 100 项测试通过和 1 项测试失败这一事实对您没有真正的帮助。这不是编程竞赛 :)
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
,不如生成 s
和 a
,然后检查属性。
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.
这种方法不需要掌握一些高级技能。
StateT 在Control.Monad.Trans.State.Lazy
里面的功能和m
更高级的东西很难
{-# 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)
你的问题很有意思。确实,使用 QuickCheck
来验证 functor/apllicative/monad 法则对于 StateT
monad 转换器来说会非常好。因为这是 QuickCheck
最有用的应用之一。
但是为 StateT
编写 Arbitrary
实例并不简单。这是可能的。但实际上并没有任何利润。您应该以某种巧妙的方式使用 CoArbitrary
类型 class。还有一些扩展。 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 Show
用于 StateT
而你不能为它编写合理的实例 :( 这就是如何QuickCheck
有效。它应该打印失败的反例。如果您不知道哪个测试失败,那么知道 100 项测试通过和 1 项测试失败这一事实对您没有真正的帮助。这不是编程竞赛 :)
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
,不如生成 s
和 a
,然后检查属性。
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.
这种方法不需要掌握一些高级技能。