对我的其他答案不满意,我想出了一个更棒的答案。
-- arb.hs
import Test.QuickCheck
import Control.Monad (liftM)
data SimpleType = SimpleType Int Char Bool String deriving(Show, Eq)
uncurry4 f (a,b,c,d) = f a b c d
instance Arbitrary SimpleType where
arbitrary = uncurry4 SimpleType `liftM` arbitrary
-- ^ this line is teh pwnzors.
-- Note how easily it can be adapted to other "simple" data types
ghci> :l arb.hs
[1 of 1] Compiling Main ( arb.hs, interpreted )
Ok, modules loaded: Main.
ghci> sample (arbitrary :: Gen SimpleType)
>>>a bunch of "Loading package" statements<<<
SimpleType 1 'B' False ""
SimpleType 0 '\n' True ""
SimpleType 0 '\186' False "\208! \227"
...
冗长的解释我是如何解决这个问题的
所以这就是我得到它的方式。我想知道,“那么怎么已经有一个Arbitrary
实例了(Int, Int, Int, Int)
?我确定没有人写过它,所以它必须以某种方式派生出来。果然,我在文档中找到了 Arbitrary 实例的以下内容:
(Arbitrary a, Arbitrary b, Arbitrary c, Arbitrary d) => Arbitrary (a, b, c, d)
好吧,如果他们已经定义了,那为什么不滥用它呢?仅由较小的任意数据类型组成的简单类型与元组没有太大区别。
所以现在我需要以某种方式转换 4 元组的“任意”方法,使其适用于我的类型。可能涉及到 Uncurrying。
停止。胡歌时间!
(我们可以很容易地定义自己的uncurry4
,所以假设我们已经有了它可以操作。)
我有一个生成器arbitrary :: Gen (q,r,s,t)
(其中 q,r,s,t 都是 Arbitrary 的实例)。但我们只是说它是arbitrary :: Gen a
. 换句话说,a
代表(q,r,s,t)
。我有一个函数,uncurry4
它的类型为(q -> r -> s -> t -> b) -> (q,r,s,t) -> b
。我们显然要将 uncurry4 应用于我们的SimpleType
构造函数。uncurry4 SimpleType
类型也是如此(q,r,s,t) -> SimpleType
。不过,让我们保持返回值通用,因为 Hoogle 不知道我们的 SimpleType。所以记住我们的定义a
,我们基本上有uncurry4 SimpleType :: a -> b
。
所以我有一个Gen a
和一个函数a -> b
。我想要一个Gen b
结果。(请记住,对于我们的情况,a
is(q,r,s,t)
和b
is SimpleType
)。所以我正在寻找具有这种类型签名的函数:Gen a -> (a -> b) -> Gen b
. 搜索,并知道这Gen
是 的一个实例Monad
,我立即认识到liftM
这是解决我的问题的单子魔法解决方案。
Hoogle 再次挽救了局面。我知道可能有一些“提升”组合器来获得所需的结果,但老实说,直到我 hoog 了类型签名,我才想到使用 liftM(durrr!)。