packages feed

recover-rtti-0.4.0.0: tests/Test/RecoverRTTI/QuickCheck/Sized.hs

{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE DeriveFunctor              #-}
{-# LANGUAGE DerivingStrategies         #-}
{-# LANGUAGE FlexibleContexts           #-}
{-# LANGUAGE GADTs                      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures             #-}
{-# LANGUAGE RankNTypes                 #-}
{-# LANGUAGE ScopedTypeVariables        #-}
{-# LANGUAGE TypeApplications           #-}
{-# LANGUAGE TypeOperators              #-}

-- | Sized generators
--
-- Intended for qualified import
--
-- > import Test.RecoverRTTI.QuickCheck.Sized (SizedGen)
-- > import qualified Test.RecoverRTTI.QuickCheck.Sized as SG
module Test.RecoverRTTI.QuickCheck.Sized (
    -- * Sized generators
    SizedGen(..)
  , run
    -- * Lifting @Gen@ into @SizedGen@
  , lift
  , arbitrary
    -- * Combinators
  , suchThat
  , withSize
  , leafOrStep
  , oneofStepped
  , replicate
  , divvy
  , divvyPair
    -- ** Derived
  , genListLike
  , genMapLike
    -- ** Support for tuples
  , ValidTuple(..)
  , genTuple
  ) where

import Prelude hiding (replicate)

import Data.Kind
import Data.SOP
import Data.SOP.Dict

import Debug.RecoverRTTI

import Test.QuickCheck (Arbitrary, Gen)

import qualified Test.QuickCheck as QC

{-------------------------------------------------------------------------------
  Sized generators
-------------------------------------------------------------------------------}

-- | Sized generators
--
-- We thread the size all the way through the generating, to avoid generating
-- very big trees. This is nonetheless a naive approach; we might want to look
-- at papers such as "Feat: Functional Enumeration of Algebraic Types".
newtype SizedGen a = SizedGen { unSizedGen :: Int -> Gen a }
  deriving (Functor)

instance Applicative SizedGen where
  pure x  = SizedGen $ \_sz -> pure x
  f <*> x = SizedGen $ \ sz -> unSizedGen f sz <*> unSizedGen x sz

run :: Int -> SizedGen a -> Gen a
run n (SizedGen gen) = gen n

{-------------------------------------------------------------------------------
  Lifting @Gen@ into @SizedGen@
-------------------------------------------------------------------------------}

lift :: Gen a -> SizedGen a
lift gen = SizedGen $ \_ -> gen

arbitrary :: Arbitrary a => SizedGen a
arbitrary = lift QC.arbitrary

{-------------------------------------------------------------------------------
  Combinators
-------------------------------------------------------------------------------}

suchThat :: SizedGen a -> (a -> Bool) -> SizedGen a
gen `suchThat` p = SizedGen $ \sz -> unSizedGen gen sz `QC.suchThat` p

withSize :: (Int -> Int) -> SizedGen a -> SizedGen a
withSize f gen = SizedGen $ unSizedGen gen . f

leafOrStep :: Gen a -> [SizedGen a] -> SizedGen a
leafOrStep leaf nested = SizedGen $ \sz ->
    if sz > 1
      then QC.oneof (map (run (sz - 1)) nested)
      else leaf

oneofStepped :: [SizedGen a] -> SizedGen a
oneofStepped gens = SizedGen $ \sz -> QC.oneof $ map (run (sz - 1)) gens

replicate :: (Int, Int) -> SizedGen a -> SizedGen [a]
replicate (lo, hi) gen = SizedGen $ \sz -> do
    n <- QC.choose (lo, max lo (min sz hi))
    let sz' = (sz - 1) `div` n
    QC.vectorOf n $ run sz' gen

divvy :: forall xs. SListI xs => NP SizedGen xs -> SizedGen (NP I xs)
divvy = hsequence . hmap (withSize (`div` n))
  where
    n = lengthSList (Proxy @xs)

divvyPair :: SizedGen a -> SizedGen b -> SizedGen (a, b)
divvyPair ga gb = unwrapTuple . tupleFromNP <$> divvy (ga :* gb :* Nil)

{-------------------------------------------------------------------------------
  Derived combinators
-------------------------------------------------------------------------------}

genListLike :: ([a] -> x) -> SizedGen a -> SizedGen x
genListLike f = fmap f . replicate (1, 5)

genMapLike :: ([(a, b)] -> x) -> SizedGen a -> SizedGen b -> SizedGen x
genMapLike f genA genB = fmap f $ replicate (1, 5) $ divvyPair genA genB

{-------------------------------------------------------------------------------
  Support for tuples
-------------------------------------------------------------------------------}

data ValidTuple f (xs :: [Type]) where
  ValidTuple :: (SListI xs, IsValidSize (Length xs)) => NP f xs -> ValidTuple f x

-- | Generate arbitrary tuple
--
-- Precondition: the generator must be able to generate values for @size >= 1@.
genTuple :: forall f. SizedGen (Some f) -> SizedGen (Some (ValidTuple f))
genTuple gen = SizedGen $ \sz -> do
    -- Pick no less than 2, and no more than 62
    n <- QC.choose (2, max 2 (min 62 sz))
    case toValidSize n of
      Nothing -> error "impossible, we pick a valid tuple size"
      Just (Some validSize@(ValidSize n' _)) ->
        case liftValidSize validSize of
          Dict -> go (sz `div` n) n' $ return . Some . ValidTuple
  where
    go :: Int
       -> SNat n
       -> (forall xs. (SListI xs, Length xs ~ n) => NP f xs -> Gen r)
       -> Gen r
    go _    SZ     k = k Nil
    go sz' (SS s) k = go sz' s $ \xs -> do
        Some x <- run sz' gen
        k $ (x :* xs)