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 :(得分:3)
你的问题非常有趣。实际上,使用QuickCheck
StateT
monad变换器验证functor / apllicative / monad定律是非常好的。因为这是QuickCheck
最有用的应用程序之一。
但是为Arbitrary
编写StateT
实例并非易事。这是可能的。但实际上它没有任何利润。您应该以某种聪明的方式使用CoArbitrary
类型类。还有几个扩展。这个想法在this blog post中描述。拥有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
需要StateT
,并且您无法为其编写合理的实例:(它只是{{{{它应该打印失败的反例。如果你不知道哪一个失败,那么知道100个测试通过和1个测试失败的事实并没有真正帮助你。这不是一个编程竞赛:)
QuickCheck
因此,生成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
这种方法并不需要掌握一些高级技能。