-- | Generic implementations of -- [QuickCheck](https://hackage.haskell.org/package/QuickCheck)'s -- @arbitrary@. -- -- == Example -- -- Define your type. -- -- @ -- data Tree a = Leaf a | Node (Tree a) (Tree a) -- deriving 'Generic' -- @ -- -- Pick an 'arbitrary' implementation, specifying the required distribution of -- data constructors. -- -- @ -- instance Arbitrary a => Arbitrary (Tree a) where -- arbitrary = 'genericArbitrary' (8 '%' 9 '%' ()) -- @ -- -- @arbitrary :: 'Gen' (Tree a)@ picks a @Leaf@ with probability 9\/17, or a -- @Node@ with probability 8\/17, and recursively fills their fields with -- @arbitrary@. -- -- For @Tree@, 'genericArbitrary' produces code equivalent to the following: -- -- @ -- 'genericArbitrary' :: Arbitrary a => 'Weights' (Tree a) -> Gen (Tree a) -- 'genericArbitrary' (x '%' y '%' ()) = -- frequency -- [ (x, Leaf \<$\> arbitrary) -- , (y, Node \<$\> arbitrary \<*\> arbitrary) -- ] -- @ -- -- == Distribution of constructors -- -- The distribution of constructors can be specified as -- a special list of /weights/ in the same order as the data type definition. -- This assigns to each constructor a probability proportional to its weight; -- in other words, @p_C = weight_C / sumOfWeights@. -- -- The list of weights is built up with the @('%')@ operator as a cons, and using -- the unit @()@ as the empty list, in the order corresponding to the data type -- definition. The uniform distribution can be obtained with 'uniform'. -- -- === Uniform distribution -- -- You can specify the uniform distribution (all weights equal) with 'uniform'. -- ('genericArbitraryU' is available as a shorthand for -- @'genericArbitrary' 'uniform'@.) -- -- Note that for many recursive types, a uniform distribution tends to produce -- big or even infinite values. -- -- === Typed weights -- -- /GHC 8.0.1 and above only (base ≥ 4.9)./ -- -- The weights actually have type @'W' \"ConstructorName\"@ (just a newtype -- around 'Int'), so that you can annotate a weight with its corresponding -- constructor, and it will be checked that you got the order right. -- -- This will type-check. -- -- @ -- ((x :: 'W' \"Leaf\") '%' (y :: 'W' \"Node\") '%' ()) :: 'Weights' (Tree a) -- (x '%' (y :: 'W' \"Node\") '%' ()) :: 'Weights' (Tree a) -- @ -- -- This will not. -- -- @ -- ((x :: 'W' \"Node\") '%' y '%' ()) :: 'Weights' (Tree a) -- -- Requires an order of constructors different from the definition of the @Tree@ type. -- -- (x '%' y '%' z '%' ()) :: 'Weights' (Tree a) -- -- Doesn't have the right number of weights. -- @ -- -- == Ensuring termination -- -- As mentioned earlier, one must be careful with recursive types -- to avoid producing extremely large values. -- -- The alternative generator 'genericArbitrary'' implements a simple strategy to keep -- values at reasonable sizes: the size parameter of 'Gen' is divided among the -- fields of the chosen constructor. When it reaches zero, the generator -- selects a small term of the given type. This generally ensures that the -- number of constructors remains close to the initial size parameter passed to -- 'Gen'. -- -- @ -- 'genericArbitrary'' (x1 '%' ... '%' xn '%' ()) -- @ -- -- Here is an example with nullary constructors: -- -- @ -- data Bush = Leaf1 | Leaf2 | Node3 Bush Bush Bush -- deriving Generic -- -- instance Arbitrary Bush where -- arbitrary = 'genericArbitrary'' (1 '%' 2 '%' 3 '%' ()) -- @ -- -- Here, 'genericArbitrary'' is equivalent to: -- -- @ -- 'genericArbitrary'' :: 'Weights' Bush -> Gen Bush -- 'genericArbitrary'' (x '%' y '%' z '%' ()) = -- sized $ \\n -> -- if n == 0 then -- -- If the size parameter is zero, only nullary alternatives are kept. -- elements [Leaf1, Leaf2] -- else -- frequency -- [ (x, return Leaf1) -- , (y, return Leaf2) -- , (z, resize (n \`div\` 3) node) -- 3 because Node3 is 3-ary -- ] -- where -- node = Node3 \<$\> arbitrary \<*\> arbitrary \<*\> arbitrary -- @ -- -- If we want to generate a value of type @Tree ()@, there is a -- value of depth 1 that we can use to end recursion: @Leaf ()@. -- -- @ -- 'genericArbitrary'' :: 'Weights' (Tree ()) -> Gen (Tree ()) -- 'genericArbitrary'' (x '%' y '%' ()) = -- sized $ \\n -> -- if n == 0 then -- return (Leaf ()) -- else -- frequency -- [ (x, Leaf \<$\> arbitrary) -- , (y, resize (n \`div\` 2) $ Node \<$\> arbitrary \<*\> arbitrary) -- ] -- @ -- -- Because the argument of @Tree@ must be inspected in order to discover -- values of type @Tree ()@, we incur some extra constraints if we want -- polymorphism. -- -- @ -- {-\# LANGUAGE FlexibleContexts, UndecidableInstances \#-} -- -- instance (Arbitrary a, BaseCase (Tree a)) -- => Arbitrary (Tree a) where -- arbitrary = 'genericArbitrary'' (1 '%' 2 '%' ()) -- @ -- -- By default, the 'BaseCase' type class looks for all values of minimal depth -- (constructors have depth @1 + max(0, depths of fields)@). -- -- This can easily be overriden by declaring a specialized 'BaseCase' instance, -- such as this one: -- -- @ -- instance Arbitrary a => 'BaseCase' (Tree a) where -- 'baseCase' = oneof [leaf, simpleNode] -- where -- leaf = Leaf \<$\> arbitrary -- simpleNode = Node \<$\> leaf \<*\> leaf -- @ -- -- An alternative base case can also be specified directly in the `arbitrary` -- definition with the 'withBaseCase' combinator. -- -- 'genericArbitraryRec' is a variant of 'genericArbitrary'' with no base case. -- -- @ -- instance Arbitrary Bush where -- arbitrary = -- 'genericArbitraryRec' (1 '%' 2 '%' 3 '%' ()) -- \`withBaseCase\` return Leaf1 -- @ -- -- == Custom generators for some fields -- -- Sometimes, a few fields may need custom generators instead of 'arbitrary'. -- For example, imagine here that String is meant to represent -- alphanumerical strings only, and that IDs are meant to be nonnegative, -- whereas balances can have any sign. -- -- @ -- data User = User { -- userName :: String, -- userId :: Int, -- userBalance :: Int -- } deriving 'Generic' -- @ -- -- - @'Arbitrary' String@ may generate any unicode characters, -- alphanumeric or not; -- - @'Arbitrary' Int@ may generate negative values; -- - using @newtype@ wrappers or passing generators explicitly to properties -- may be impractical (the maintenance overhead can be high because the types -- are big or change often). -- -- Using generic-random, the alternative is to declare a (heterogeneous) list -- of generators to be used when generating certain fields... -- -- @ -- customGens :: 'GenList' '['Field' "userId" Int, String] -- customGens = -- ('Field' . 'getNonNegative' \<$\> arbitrary) ':@' -- ('listOf' ('elements' (filter isAlphaNum [minBound .. maxBound]))) ':@' -- 'Nil' -- @ -- -- And to use the 'genericArbitraryG' and variants that accept those explicit -- generators. -- -- - All @String@ fields will use the provided generator of -- alphanumeric strings; -- - the field @"userId"@ of type @Int@ will use the generator -- of nonnegative integers (the 'Field' type is special); -- - everything else defaults to 'arbitrary'. -- -- @ -- instance Arbitrary User where -- arbitrary = 'genericArbitrarySingleG' customGens -- @ module Generic.Random.Tutorial () where import GHC.Generics import Generic.Random