generic-random-0.3.0.0: src/Generic/Random/Internal/Generic.hs
{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DeriveFunctor, GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ConstraintKinds #-}
module Generic.Random.Internal.Generic where
import Control.Applicative
import Data.Coerce
import GHC.Generics hiding ( S )
import Test.QuickCheck
-- * Random generators
-- | Pick a constructor with uniform probability, and fill its fields
-- recursively.
--
-- An equivalent definition for @Tree@ is:
--
-- > genericArbitrary :: Arbitrary a => Gen (Tree a)
-- > genericArbitrary =
-- > oneof
-- > [ Leaf <$> arbitrary -- Uses Arbitrary a
-- > , Node <$> arbitrary <*> arbitrary -- Uses Arbitrary (Tree a)
-- > ]
--
-- Note that for many types, 'genericArbitrary' tends to produce big values.
-- For instance for @Tree a@ values are finite but the average number of
-- @Leaf@ and @Node@ constructors is infinite.
genericArbitrary :: forall a. (Generic a, GA Unsized (Rep a)) => Gen a
genericArbitrary =
(($ repeat 1) . unFreq . fmap to) (ga :: Freq Unsized (Rep a p))
-- | This allows to specify the probability distribution of constructors
-- as a list of weights, in the same order as the data type definition.
--
-- An equivalent definition for @Tree@ is:
--
-- > genericArbitraryFrequency :: Arbitrary a => [Int] -> Gen (Tree a)
-- > genericArbitraryFrequency [x, y] =
-- > frequency
-- > [ (x, Leaf <$> arbitrary)
-- > , (y, Node <$> arbitrary <*> arbitrary)
-- > ]
genericArbitraryFrequency
:: forall a. (Generic a, GA Unsized (Rep a))
=> [Int] -- ^ List of weights for every constructor
-> Gen a
genericArbitraryFrequency = (unFreq . fmap to) (ga :: Freq Unsized (Rep a p))
-- | The size parameter of 'Gen' is divided among the fields of the chosen
-- constructor. When it reaches zero, the generator selects a finite term
-- whenever it can find any of the given type.
--
-- The natural number @n@ determines the maximum /depth/ of terms that can be
-- used to end recursion.
-- It is encoded using @'Z' :: 'Z'@ and @'S' :: n -> 'S' n@.
--
-- > genericArbitraryFrequency' n weights
--
-- With @n = 'Z'@, the generator looks for a simple nullary constructor. If none
-- exist at the current type, as is the case for our @Tree@ type, it carries on
-- as in 'genericArbitraryFrequency'.
--
-- > genericArbitraryFrequency' Z :: Arbitrary a => [Int] -> Gen (Tree a)
-- > genericArbitraryFrequency' Z [x, y] =
-- > frequency
-- > [ (x, Leaf <$> arbitrary)
-- > , (y, scale (`div` 2) $ Node <$> arbitrary <*> arbitrary)
-- > ]
-- > -- 2 because Node is 2-ary.
--
-- Here is another example:
--
-- > data Tree' = Leaf1 | Leaf2 | Node3 Tree' Tree' Tree'
-- > deriving Generic
-- >
-- > instance Arbitrary Tree' where
-- > arbitrary = genericArbitraryFrequency' Z [1, 2, 3]
--
-- 'genericArbitraryFrequency'' is equivalent to:
--
-- > genericArbitraryFrequency' Z :: [Int] -> Gen Tree'
-- > genericArbitraryFrequency' Z [x, y, z] =
-- > sized $ \n ->
-- > if n == 0 then
-- > -- If the size parameter is zero, the non-nullary alternative is discarded.
-- > frequency $
-- > [ (x, return Leaf1)
-- > , (y, return 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
--
-- To increase the chances of termination when no nullary constructor is directly
-- available, such as in @Tree@, we can pass a larger depth @n@. The effectiveness
-- of this parameter depends on the concrete type the generator is used for.
--
-- For instance, if we want to generate a value of type @Tree ()@, there is a
-- value of depth 1 (represented by @'S' 'Z'@) that we can use to end
-- recursion: @Leaf ()@.
--
-- > genericArbitraryFrequency' (S Z) :: [Int] -> Gen (Tree ())
-- > genericArbitraryFrequency' (S Z) [x, y] =
-- > sized $ \n ->
-- > if n == 0 then
-- > return (Leaf ())
-- > else
-- > frequency
-- > [ (x, Leaf <$> arbitrary)
-- > , (y, scale (`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.
--
-- @FlexibleContexts@ and @UndecidableInstances@ are also required.
--
-- > instance (Arbitrary a, Generic a, BaseCases Z (Rep a))
-- > => Arbitrary (Tree a) where
-- > arbitrary = genericArbitraryFrequency' (S Z) [1, 2]
--
-- A synonym is provided for brevity.
--
-- > instance (Arbitrary a, BaseCases' Z a) => Arbitrary (Tree a) where
-- > arbitrary = genericArbitraryFrequency' (S Z) [1, 2]
genericArbitraryFrequency'
:: forall n a
. (Generic a, GA (Sized n) (Rep a))
=> n
-> [Int] -- ^ List of weights for every constructor
-> Gen a
genericArbitraryFrequency' _ =
(unFreq . fmap to) (ga :: Freq (Sized n) (Rep a p))
-- | Like 'genericArbitraryFrequency'', but with uniformly distributed
-- constructors.
genericArbitrary'
:: forall n a
. (Generic a, GA (Sized n) (Rep a)) => n -> Gen a
genericArbitrary' _ =
(($ repeat 1) . unFreq . fmap to) (ga :: Freq (Sized n) (Rep a p))
-- * Internal
newtype Freq sized a = Freq { unFreq :: [Int] -> Gen a }
deriving Functor
instance Applicative (Freq sized) where
pure = Freq . pure . pure
Freq f <*> Freq x = Freq (liftA2 (<*>) f x)
newtype Gen' sized a = Gen' { unGen' :: Gen a }
deriving (Functor, Applicative)
data Sized n
data Unsized
liftGen :: Gen a -> Freq sized a
liftGen = Freq . const
-- | Generic Arbitrary
class GA sized f where
ga :: Freq sized (f p)
instance GA sized U1 where
ga = pure U1
instance Arbitrary c => GA sized (K1 i c) where
ga = liftGen . fmap K1 $ arbitrary
instance GA sized f => GA sized (M1 i c f) where
ga = fmap M1 ga
instance (GASum (Sized n) f, GASum (Sized n) g, BaseCases n f, BaseCases n g)
=> GA (Sized n) (f :+: g) where
ga = frequency' gaSum baseCases
where
frequency' :: [Gen' sized a] -> Tagged n [[a]] -> Freq sized a
frequency' as (Tagged a0s) = Freq $ \ws ->
let
units = [(w, elements a0) | (w, a0@(_ : _)) <- zip ws a0s]
in
sized $ \sz -> frequency $
if sz == 0 && not (null units) then
units
else
[(w, a) | (w, Gen' a) <- zip ws as]
instance (GASum Unsized f, GASum Unsized g) => GA Unsized (f :+: g) where
ga = frequency' gaSum
where
frequency' :: [Gen' sized a] -> Freq sized a
frequency' as = Freq $ \ws -> frequency
[(w, a) | (w, Gen' a) <- zip ws as]
instance (GA Unsized f, GA Unsized g) => GA Unsized (f :*: g) where
ga = liftA2 (:*:) ga ga
instance (GAProduct f, GAProduct g) => GA (Sized n) (f :*: g) where
ga = constScale' a
where
constScale' :: Gen' Unsized a -> Freq (Sized n) a
constScale' = Freq . const . scale (`div` arity) . unGen'
(arity, a) = gaProduct
gArbitrarySingle :: forall sized f p . GA sized f => Gen' sized (f p)
gArbitrarySingle = Gen' (unFreq (ga :: Freq sized (f p)) [1])
class GASum sized f where
gaSum :: [Gen' sized (f p)]
instance (GASum sized f, GASum sized g) => GASum sized (f :+: g) where
gaSum = (fmap . fmap) L1 gaSum ++ (fmap . fmap) R1 gaSum
instance GA sized f => GASum sized (M1 i c f) where
gaSum = [gArbitrarySingle]
class GAProduct f where
gaProduct :: (Int, Gen' Unsized (f p))
instance GA Unsized f => GAProduct (M1 i c f) where
gaProduct = (1, gArbitrarySingle)
instance (GAProduct f, GAProduct g) => GAProduct (f :*: g) where
gaProduct = (m + n, liftA2 (:*:) a b)
where
(m, a) = gaProduct
(n, b) = gaProduct
newtype Tagged a b = Tagged { unTagged :: b }
-- $nat
-- Use the 'Z' and 'S' data types to define the depths of values used
-- by 'genericArbitraryFrequency'' and 'genericArbitrary'' to make
-- generators terminate.
-- | Zero
data Z = Z
-- | Successor
data S n = S n
-- | A @BaseCases n ('Rep' a)@ constraint basically provides the list of values
-- of type @a@ with depth at most @n@.
class BaseCases n f where
baseCases :: Tagged n [[f p]]
-- | For convenience.
type BaseCases' n a = (Generic a, BaseCases n (Rep a))
baseCases' :: forall n f p. BaseCases n f => Tagged n [f p]
baseCases' = (Tagged . concat . unTagged) (baseCases :: Tagged n [[f p]])
instance BaseCases n U1 where
baseCases = Tagged [[U1]]
instance BaseCases n f => BaseCases n (M1 i c f) where
baseCases = (coerce :: Tagged n [[f p]] -> Tagged n [[M1 i c f p]]) baseCases
instance BaseCases Z (K1 i c) where
baseCases = Tagged [[]]
instance (Generic c, BaseCases n (Rep c)) => BaseCases (S n) (K1 i c) where
baseCases =
(Tagged . (fmap . fmap) (K1 . to) . unTagged)
(baseCases :: Tagged n [[Rep c p]])
instance (BaseCases n f, BaseCases n g) => BaseCases n (f :+: g) where
baseCases = Tagged $
((fmap . fmap) L1 . unTagged) (baseCases :: Tagged n [[f p]]) ++
((fmap . fmap) R1 . unTagged) (baseCases :: Tagged n [[g p]])
instance (BaseCases n f, BaseCases n g) => BaseCases n (f :*: g) where
baseCases = Tagged
[ liftA2 (:*:)
(unTagged (baseCases' :: Tagged n [f p]))
(unTagged (baseCases' :: Tagged n [g p])) ]