generic-random-1.5.0.1: src/Generic/Random/DerivingVia.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_HADDOCK not-home #-}
module Generic.Random.DerivingVia
( GenericArbitrary (..),
GenericArbitraryU (..),
GenericArbitrarySingle (..),
GenericArbitraryRec (..),
GenericArbitraryG (..),
GenericArbitraryUG (..),
GenericArbitrarySingleG (..),
GenericArbitraryRecG (..),
GenericArbitraryWith (..),
AndShrinking (..),
TypeLevelGenList (..),
TypeLevelOpts (..),
)
where
import Data.Coerce (Coercible, coerce)
import Data.Kind (Type)
import Data.Proxy (Proxy (..))
import GHC.Generics (Generic(..))
import GHC.TypeLits (KnownNat, natVal)
import Generic.Random.Internal.Generic
import Test.QuickCheck (Arbitrary (..), Gen, genericShrink)
import Test.QuickCheck.Arbitrary (RecursivelyShrink, GSubterms)
-- * Newtypes for DerivingVia
-- | Pick a constructor with a given distribution, and fill its fields
-- with recursive calls to 'Test.QuickCheck.arbitrary'.
--
-- === Example
--
-- > data X = ...
-- > deriving Arbitrary via (GenericArbitrary '[2, 3, 5] X)
--
-- Picks the first constructor with probability @2/10@,
-- the second with probability @3/10@, the third with probability @5/10@.
--
-- This newtype does no shrinking. To add generic shrinking, use 'AndShrinking'.
--
-- Uses 'genericArbitrary'.
--
-- @since 1.5.0.0
newtype GenericArbitrary weights a = GenericArbitrary {unGenericArbitrary :: a} deriving (Eq, Show)
instance
( GArbitrary UnsizedOpts a,
TypeLevelWeights' weights a
) =>
Arbitrary (GenericArbitrary weights a)
where
arbitrary = GenericArbitrary <$> genericArbitrary (typeLevelWeights @weights)
-- | Pick every constructor with equal probability.
--
-- This newtype does no shrinking. To add generic shrinking, use 'AndShrinking'.
--
-- Uses 'genericArbitraryU'.
--
-- @since 1.5.0.0
newtype GenericArbitraryU a = GenericArbitraryU {unGenericArbitraryU :: a} deriving (Eq, Show)
instance
( GArbitrary UnsizedOpts a,
GUniformWeight a
) =>
Arbitrary (GenericArbitraryU a)
where
arbitrary = GenericArbitraryU <$> genericArbitraryU
-- | @arbitrary@ for types with one constructor.
-- Equivalent to 'GenericArbitraryU', with a stricter type.
--
-- This newtype does no shrinking. To add generic shrinking, use 'AndShrinking'.
--
-- Uses 'genericArbitrarySingle'.
--
-- @since 1.5.0.0
newtype GenericArbitrarySingle a = GenericArbitrarySingle {unGenericArbitrarySingle :: a} deriving (Eq, Show)
instance
( GArbitrary UnsizedOpts a,
Weights_ (Rep a) ~ L c0
) =>
Arbitrary (GenericArbitrarySingle a)
where
arbitrary = GenericArbitrarySingle <$> genericArbitrarySingle
-- | Decrease size at every recursive call, but don't do anything different
-- at size 0.
--
-- > data X = ...
-- > deriving Arbitrary via (GenericArbitraryRec '[2, 3, 5] X)
--
-- N.B.: This replaces the generator for fields of type @[t]@ with
-- @'listOf'' arbitrary@ instead of @'Test.QuickCheck.listOf' arbitrary@ (i.e., @arbitrary@ for
-- lists).
--
-- This newtype does no shrinking. To add generic shrinking, use 'AndShrinking'.
--
-- Uses 'genericArbitraryRec'.
--
-- @since 1.5.0.0
newtype GenericArbitraryRec weights a = GenericArbitraryRec {unGenericArbitraryRec :: a} deriving (Eq, Show)
instance
( GArbitrary SizedOptsDef a,
TypeLevelWeights' weights a
) =>
Arbitrary (GenericArbitraryRec weights a)
where
arbitrary = GenericArbitraryRec <$> genericArbitraryRec (typeLevelWeights @weights)
-- | 'GenericArbitrary' with explicit generators.
--
-- === Example
--
-- > data X = ...
-- > deriving Arbitrary via (GenericArbitraryG CustomGens '[2, 3, 5] X)
--
-- where, for example, custom generators to override 'String' and 'Int' fields
-- might look as follows:
--
-- @
-- type CustomGens = CustomString ':+' CustomInt
-- @
--
-- === Note on multiple matches
--
-- Multiple generators may match a given field: the first will be chosen.
--
-- This newtype does no shrinking. To add generic shrinking, use 'AndShrinking'.
--
-- Uses 'genericArbitraryG'.
--
-- @since 1.5.0.0
newtype GenericArbitraryG genList weights a = GenericArbitraryG {unGenericArbitraryG :: a} deriving (Eq, Show)
instance
( GArbitrary (SetGens genList UnsizedOpts) a,
GUniformWeight a,
TypeLevelWeights' weights a,
TypeLevelGenList genList',
genList ~ TypeLevelGenList' genList'
) =>
Arbitrary (GenericArbitraryG genList' weights a)
where
arbitrary = GenericArbitraryG <$> genericArbitraryG (toGenList $ Proxy @genList') (typeLevelWeights @weights)
-- | 'GenericArbitraryU' with explicit generators.
-- See also 'GenericArbitraryG'.
--
-- This newtype does no shrinking. To add generic shrinking, use 'AndShrinking'.
--
-- Uses 'genericArbitraryUG'.
--
-- @since 1.5.0.0
newtype GenericArbitraryUG genList a = GenericArbitraryUG {unGenericArbitraryUG :: a} deriving (Eq, Show)
instance
( GArbitrary (SetGens genList UnsizedOpts) a,
GUniformWeight a,
TypeLevelGenList genList',
genList ~ TypeLevelGenList' genList'
) =>
Arbitrary (GenericArbitraryUG genList' a)
where
arbitrary = GenericArbitraryUG <$> genericArbitraryUG (toGenList $ Proxy @genList')
-- | 'genericArbitrarySingle' with explicit generators.
-- See also 'GenericArbitraryG'.
--
-- This newtype does no shrinking. To add generic shrinking, use 'AndShrinking'.
--
-- Uses 'genericArbitrarySingleG'.
--
-- @since 1.5.0.0
newtype GenericArbitrarySingleG genList a = GenericArbitrarySingleG {unGenericArbitrarySingleG :: a} deriving (Eq, Show)
instance
( GArbitrary (SetGens genList UnsizedOpts) a,
Weights_ (Rep a) ~ L c0,
TypeLevelGenList genList',
genList ~ TypeLevelGenList' genList'
) =>
Arbitrary (GenericArbitrarySingleG genList' a)
where
arbitrary = GenericArbitrarySingleG <$> genericArbitrarySingleG (toGenList $ Proxy @genList')
-- | 'genericArbitraryRec' with explicit generators.
-- See also 'genericArbitraryG'.
--
-- This newtype does no shrinking. To add generic shrinking, use 'AndShrinking'.
--
-- Uses 'genericArbitraryRecG'.
--
-- @since 1.5.0.0
newtype GenericArbitraryRecG genList weights a = GenericArbitraryRecG {unGenericArbitraryRecG :: a} deriving (Eq, Show)
instance
( GArbitrary (SetGens genList SizedOpts) a,
TypeLevelWeights' weights a,
TypeLevelGenList genList',
genList ~ TypeLevelGenList' genList'
) =>
Arbitrary (GenericArbitraryRecG genList' weights a)
where
arbitrary = GenericArbitraryRecG <$> genericArbitraryRecG (toGenList $ Proxy @genList') (typeLevelWeights @weights)
-- | General generic generator with custom options.
--
-- This newtype does no shrinking. To add generic shrinking, use 'AndShrinking'.
--
-- Uses 'genericArbitraryWith'.
--
-- @since 1.5.0.0
newtype GenericArbitraryWith opts weights a = GenericArbitraryWith {unGenericArbitraryWith :: a} deriving (Eq, Show)
instance
( GArbitrary opts a,
TypeLevelWeights' weights a,
TypeLevelOpts opts',
opts ~ TypeLevelOpts' opts'
) =>
Arbitrary (GenericArbitraryWith opts' weights a)
where
arbitrary = GenericArbitraryWith <$> genericArbitraryWith (toOpts $ Proxy @opts') (typeLevelWeights @weights)
-- | Add generic shrinking to a newtype wrapper for 'Arbitrary', using 'genericShrink'.
--
-- @
-- data X = ...
-- deriving Arbitrary via ('GenericArbitrary' '[1,2,3] `'AndShrinking'` X)
-- @
--
-- Equivalent to:
--
-- @
-- instance Arbitrary X where
-- arbitrary = 'genericArbitrary' (1 % 2 % 3 % ())
-- shrink = 'Test.QuickCheck.genericShrink'
-- @
--
-- @since 1.5.0.0
newtype AndShrinking f a = AndShrinking a deriving (Eq, Show)
instance
( Arbitrary (f a), Coercible (f a) a, Generic a, RecursivelyShrink (Rep a), GSubterms (Rep a) a
) => Arbitrary (AndShrinking f a) where
arbitrary = coerce (arbitrary :: Gen (f a))
shrink = coerce (genericShrink :: a -> [a])
-- * Internal
-- |
-- @since 1.5.0.0
type TypeLevelWeights' weights a = TypeLevelWeights weights (Weights_ (Rep a))
typeLevelWeights ::
forall weights a.
TypeLevelWeights weights (Weights_ (Rep a)) =>
Weights a
typeLevelWeights =
let (w, n) = typeLevelWeightsBuilder @weights
in Weights w n
-- |
-- @since 1.5.0.0
class TypeLevelWeights weights a where
typeLevelWeightsBuilder :: (a, Int)
instance
( KnownNat weight,
TypeLevelWeights weights a
) =>
TypeLevelWeights (weight ': weights) (L x :| a)
where
typeLevelWeightsBuilder =
let (a, m) = (L, fromIntegral $ natVal $ Proxy @weight)
(b, n) = typeLevelWeightsBuilder @weights @a
in (N a m b, m + n)
instance
( KnownNat weight
) =>
TypeLevelWeights (weight ': '[]) (L x)
where
typeLevelWeightsBuilder = (L, fromIntegral $ natVal $ Proxy @weight)
instance
TypeLevelWeights (w ': ws) (t :| (u :| v)) =>
TypeLevelWeights (w ': ws) ((t :| u) :| v)
where
typeLevelWeightsBuilder =
let (N t nt (N u nu v), m) = typeLevelWeightsBuilder @(w ': ws) @(t :| (u :| v))
in (N (N t nt u) (nt + nu) v, m)
instance TypeLevelWeights '[] () where
typeLevelWeightsBuilder = ((), 1)
-- |
-- @since 1.5.0.0
class TypeLevelGenList a where
type TypeLevelGenList' a :: Type
toGenList :: Proxy a -> TypeLevelGenList' a
instance Arbitrary a => TypeLevelGenList (Gen a) where
type TypeLevelGenList' (Gen a) = Gen a
toGenList _ = arbitrary
instance (TypeLevelGenList a, TypeLevelGenList b) => TypeLevelGenList (a :+ b) where
type TypeLevelGenList' (a :+ b) = TypeLevelGenList' a :+ TypeLevelGenList' b
toGenList _ = toGenList (Proxy @a) :+ toGenList (Proxy @b)
-- |
-- @since 1.5.0.0
class TypeLevelOpts a where
type TypeLevelOpts' a :: Type
toOpts :: Proxy a -> TypeLevelOpts' a