私の他の答えに満足していないので、もっと素晴らしいものを思いつきました.
-- 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 a, Arbitrary b, Arbitrary c, Arbitrary d) => Arbitrary (a, b, c, d)
ええと、彼らがすでにそれを定義しているのなら、それを悪用してみませんか? より小さな Arbitrary データ型で構成されているだけの単純な型は、単なるタプルと大差ありません。
したがって、4タプルの「任意の」メソッドを何らかの方法で変換して、自分のタイプで機能するようにする必要があります。Uncurring が関与している可能性があります。
止まる。ホーグルタイム!
(独自の を簡単に定義できる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
コンストラクターに適用することは明らかです。typeuncurry4 SimpleType
も同様(q,r,s,t) -> SimpleType
です。ただし、Hoogle は SimpleType を認識していないため、戻り値は一般的なものにしておきましょう。の定義を思い出すとa
、本質的に が得られuncurry4 SimpleType :: a -> b
ます。
だから私は aGen a
と functionを持っていますa -> b
。そして結果が欲しいGen b
。a
(私たちの状況では、 is(q,r,s,t)
とb
isであることを思い出してくださいSimpleType
)。だから私はこの型シグネチャを持つ関数を探しています: Gen a -> (a -> b) -> Gen b
. それをフーグリングしGen
、それが のインスタンスであることを知って、私は自分の問題に対するモナド的で魔法のような解決策であるMonad
とすぐに認識します。liftM
Hoogle は再び危機を脱します。望ましい結果を得るには、おそらく何らかの「リフティング」コンビネータがあることは知っていましたが、正直なところ、型シグネチャを調べるまでは、liftM (durrr!) を使用することは考えていませんでした。