你的问题真的很有趣。事实上,使用以下方法验证函子/应用/单子定律会非常好QuickCheck
for StateT
单子变压器。因为这是最有用的应用之一QuickCheck
.
但写作Arbitrary
实例为StateT
这并非小事。这是可能的。但实际上并没有什么利润可言。你应该使用CoArbitrary
以某种聪明的方式输入类。还有耦合扩展。这个想法描述于这篇博文。拥有CoArbitrary
实例为a -> b
你可以轻松创建Arbitrary
实例为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)
然后你可以生成状态:
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
for 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
and 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.
这种方法不需要一些高级技能的知识。