module Hedgehog.Internal.Gen (
Gen
, GenT(..)
, MonadGen(..)
, lift
, shrink
, prune
, small
, scale
, resize
, sized
, integral
, integral_
, int
, int8
, int16
, int32
, int64
, word
, word8
, word16
, word32
, word64
, realFloat
, realFrac_
, float
, double
, enum
, enumBounded
, bool
, bool_
, binit
, octit
, digit
, hexit
, lower
, upper
, alpha
, alphaNum
, ascii
, latin1
, unicode
, unicodeAll
, string
, text
, utf8
, bytes
, constant
, element
, choice
, frequency
, recursive
, discard
, ensure
, filter
, just
, maybe
, list
, seq
, nonEmpty
, set
, map
, freeze
, subterm
, subtermM
, subterm2
, subtermM2
, subterm3
, subtermM3
, subsequence
, shuffle
, sample
, print
, printTree
, printWith
, printTreeWith
, runGenT
, mapGenT
, generate
, liftTree
, runDiscardEffect
, golden
, atLeast
, isSurrogate
, Vec(..)
, Nat(..)
, subtermMVec
, renderNodes
) where
import Control.Applicative (Alternative(..))
import Control.Monad (MonadPlus(..), filterM, replicateM, ap, join)
import Control.Monad.Base (MonadBase(..))
import Control.Monad.Catch (MonadThrow(..), MonadCatch(..))
import Control.Monad.Error.Class (MonadError(..))
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Morph (MFunctor(..), MMonad(..), generalize)
import Control.Monad.Primitive (PrimMonad(..))
import Control.Monad.Reader.Class (MonadReader(..))
import Control.Monad.State.Class (MonadState(..))
import Control.Monad.Trans.Class (MonadTrans)
import qualified Control.Monad.Trans.Class as Trans
import Control.Monad.Trans.Except (ExceptT(..), mapExceptT)
import Control.Monad.Trans.Identity (IdentityT(..), mapIdentityT)
import Control.Monad.Trans.Maybe (MaybeT(..), mapMaybeT)
import qualified Control.Monad.Trans.RWS.Lazy as Lazy
import qualified Control.Monad.Trans.RWS.Strict as Strict
import Control.Monad.Trans.Reader (ReaderT(..), mapReaderT)
import Control.Monad.Trans.Resource (MonadResource(..))
import qualified Control.Monad.Trans.State.Lazy as Lazy
import qualified Control.Monad.Trans.State.Strict as Strict
import qualified Control.Monad.Trans.Writer.Lazy as Lazy
import qualified Control.Monad.Trans.Writer.Strict as Strict
import Control.Monad.Writer.Class (MonadWriter(..))
import Data.Bifunctor (first, second)
import Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
import qualified Data.Char as Char
import Data.Foldable (for_, toList)
import Data.Functor.Identity (Identity(..))
import Data.Int (Int8, Int16, Int32, Int64)
import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as NonEmpty
import Data.Map (Map)
import qualified Data.Map as Map
import qualified Data.Maybe as Maybe
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
import Data.Set (Set)
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import Data.Word (Word8, Word16, Word32, Word64)
import Hedgehog.Internal.Distributive (Distributive(..))
import Hedgehog.Internal.Seed (Seed)
import qualified Hedgehog.Internal.Seed as Seed
import qualified Hedgehog.Internal.Shrink as Shrink
import Hedgehog.Internal.Tree (Tree(..), Node(..))
import qualified Hedgehog.Internal.Tree as Tree
import Hedgehog.Range (Size, Range)
import qualified Hedgehog.Range as Range
import Prelude hiding (filter, print, maybe, map, seq)
type Gen =
GenT Identity
newtype GenT m a =
GenT {
unGen :: Size -> Seed -> Tree (MaybeT m) a
}
runGenT :: Size -> Seed -> GenT m a -> Tree (MaybeT m) a
runGenT size seed (GenT m) =
m size seed
mapGenT :: (Tree (MaybeT m) a -> Tree (MaybeT n) b) -> GenT m a -> GenT n b
mapGenT f gen =
GenT $ \size seed ->
f (runGenT size seed gen)
liftTree :: Tree (MaybeT m) a -> GenT m a
liftTree x =
GenT (\_ _ -> x)
runDiscardEffect :: Monad m => Tree (MaybeT m) a -> Tree m (Maybe a)
runDiscardEffect =
runMaybeT . distribute
class Monad m => MonadGen m where
liftGen :: Gen a -> m a
shrinkGen :: (a -> [a]) -> m a -> m a
pruneGen :: m a -> m a
scaleGen :: (Size -> Size) -> m a -> m a
freezeGen :: m a -> m (a, m a)
instance Monad m => MonadGen (GenT m) where
liftGen gen =
hoist generalize gen
shrinkGen =
mapGenT . Tree.expand
pruneGen =
mapGenT Tree.prune
scaleGen f gen =
GenT $ \size0 seed ->
let
size =
f size0
in
if size < 0 then
error "Hedgehog.Gen.scale: negative size"
else
runGenT size seed gen
freezeGen gen =
GenT $ \size seed -> do
mx <- Trans.lift . Trans.lift . runMaybeT . runTree $ runGenT size seed gen
case mx of
Nothing ->
mzero
Just (Node x xs) ->
pure (x, liftTree . Tree.fromNode $ Node x xs)
instance MonadGen m => MonadGen (IdentityT m) where
liftGen =
Trans.lift . liftGen
shrinkGen f =
mapIdentityT (shrink f)
pruneGen =
hoist prune
scaleGen f =
hoist (scale f)
freezeGen =
mapIdentityT $
fmap (second Trans.lift) . freeze
shrinkMaybe :: (a -> [a]) -> Maybe a -> [Maybe a]
shrinkMaybe f = \case
Nothing ->
pure Nothing
Just x ->
fmap Just (f x)
shrinkEither :: (a -> [a]) -> Either x a -> [Either x a]
shrinkEither f = \case
Left x ->
pure $ Left x
Right x ->
fmap Right (f x)
shrink2 :: (a -> [a]) -> (a, b) -> [(a, b)]
shrink2 f (x, y) =
fmap (, y) (f x)
shrink3 :: (a -> [a]) -> (a, b, c) -> [(a, b, c)]
shrink3 f (x, y, z) =
fmap (, y, z) (f x)
instance MonadGen m => MonadGen (MaybeT m) where
liftGen =
Trans.lift . liftGen
shrinkGen f =
mapMaybeT $
shrink (shrinkMaybe f)
pruneGen =
hoist prune
scaleGen f =
hoist (scale f)
freezeGen =
mapMaybeT $ \m0 -> do
(mx, m) <- freeze m0
pure $ fmap (, MaybeT m) mx
instance MonadGen m => MonadGen (ExceptT x m) where
liftGen =
Trans.lift . liftGen
shrinkGen f =
mapExceptT $
shrink (shrinkEither f)
pruneGen =
hoist prune
scaleGen f =
hoist (scale f)
freezeGen =
mapExceptT $ \m0 -> do
(mx, m) <- freeze m0
pure $ fmap (, ExceptT m) mx
instance MonadGen m => MonadGen (ReaderT r m) where
liftGen =
Trans.lift . liftGen
shrinkGen f =
mapReaderT (shrink f)
pruneGen =
hoist prune
scaleGen f =
hoist (scale f)
freezeGen =
mapReaderT $
fmap (second Trans.lift) . freeze
instance MonadGen m => MonadGen (Lazy.StateT s m) where
liftGen =
Trans.lift . liftGen
shrinkGen f =
Lazy.mapStateT $
shrink (shrink2 f)
pruneGen =
hoist prune
scaleGen f =
hoist (scale f)
freezeGen m0 =
Lazy.StateT $ \s0 -> do
((x, s), m) <- freeze (Lazy.runStateT m0 s0)
pure ((x, Lazy.StateT (const m)), s)
instance MonadGen m => MonadGen (Strict.StateT s m) where
liftGen =
Trans.lift . liftGen
shrinkGen f =
Strict.mapStateT $
shrink (shrink2 f)
pruneGen =
hoist prune
scaleGen f =
hoist (scale f)
freezeGen m0 =
Strict.StateT $ \s0 -> do
((x, s), m) <- freeze (Strict.runStateT m0 s0)
pure ((x, Strict.StateT (const m)), s)
instance (MonadGen m, Monoid w) => MonadGen (Lazy.WriterT w m) where
liftGen =
Trans.lift . liftGen
shrinkGen f =
Lazy.mapWriterT $
shrink (shrink2 f)
pruneGen =
hoist prune
scaleGen f =
hoist (scale f)
freezeGen m0 =
Lazy.WriterT $ do
((x, w), m) <- freeze (Lazy.runWriterT m0)
pure ((x, Lazy.WriterT m), w)
instance (MonadGen m, Monoid w) => MonadGen (Strict.WriterT w m) where
liftGen =
Trans.lift . liftGen
shrinkGen f =
Strict.mapWriterT $
shrink (shrink2 f)
pruneGen =
hoist prune
scaleGen f =
hoist (scale f)
freezeGen m0 =
Strict.WriterT $ do
((x, w), m) <- freeze (Strict.runWriterT m0)
pure ((x, Strict.WriterT m), w)
instance (MonadGen m, Monoid w) => MonadGen (Lazy.RWST r w s m) where
liftGen =
Trans.lift . liftGen
shrinkGen f =
Lazy.mapRWST $
shrink (shrink3 f)
pruneGen =
hoist prune
scaleGen f =
hoist (scale f)
freezeGen m0 =
Lazy.RWST $ \r s0 -> do
((x, s, w), m) <- freeze (Lazy.runRWST m0 r s0)
pure ((x, Lazy.RWST (\_ _ -> m)), s, w)
instance (MonadGen m, Monoid w) => MonadGen (Strict.RWST r w s m) where
liftGen =
Trans.lift . liftGen
shrinkGen f =
Strict.mapRWST $
shrink (shrink3 f)
pruneGen =
hoist prune
scaleGen f =
hoist (scale f)
freezeGen m0 =
Strict.RWST $ \r s0 -> do
((x, s, w), m) <- freeze (Strict.runRWST m0 r s0)
pure ((x, Strict.RWST (\_ _ -> m)), s, w)
instance Functor m => Functor (GenT m) where
fmap f gen =
GenT $ \seed size ->
fmap f (runGenT seed size gen)
instance Monad m => Applicative (GenT m) where
pure =
return
(<*>) =
ap
instance Monad m => Monad (GenT m) where
return =
liftTree . pure
(>>=) m k =
GenT $ \size seed ->
case Seed.split seed of
(sk, sm) ->
runGenT size sk . k =<<
runGenT size sm m
instance Monad m => Alternative (GenT m) where
empty =
mzero
(<|>) =
mplus
instance Monad m => MonadPlus (GenT m) where
mzero =
liftTree mzero
mplus x y =
GenT $ \size seed ->
case Seed.split seed of
(sx, sy) ->
runGenT size sx x `mplus`
runGenT size sy y
instance MonadTrans GenT where
lift =
liftTree . Trans.lift . Trans.lift
instance MFunctor GenT where
hoist f =
mapGenT (hoist (hoist f))
embedMaybe ::
MonadTrans t
=> Monad n
=> Monad (t (MaybeT n))
=> (forall a. m a -> t (MaybeT n) a)
-> MaybeT m b
-> t (MaybeT n) b
embedMaybe f m =
Trans.lift . MaybeT . pure =<< f (runMaybeT m)
embedTree :: Monad n => (forall a. m a -> Tree (MaybeT n) a) -> Tree (MaybeT m) b -> Tree (MaybeT n) b
embedTree f tree =
embed (embedMaybe f) tree
embedGen :: Monad n => (forall a. m a -> GenT n a) -> GenT m b -> GenT n b
embedGen f gen =
GenT $ \size seed ->
case Seed.split seed of
(sf, sg) ->
(runGenT size sf . f) `embedTree`
(runGenT size sg gen)
instance MMonad GenT where
embed =
embedGen
distributeGen :: Transformer t GenT m => GenT (t m) a -> t (GenT m) a
distributeGen x =
join . Trans.lift . GenT $ \size seed ->
pure . hoist liftTree . distribute . hoist distribute $ runGenT size seed x
instance Distributive GenT where
type Transformer t GenT m = (
Monad (t (GenT m))
, Transformer t MaybeT m
, Transformer t Tree (MaybeT m)
)
distribute =
distributeGen
instance PrimMonad m => PrimMonad (GenT m) where
type PrimState (GenT m) =
PrimState m
primitive =
Trans.lift . primitive
instance MonadIO m => MonadIO (GenT m) where
liftIO =
Trans.lift . liftIO
instance MonadBase b m => MonadBase b (GenT m) where
liftBase =
Trans.lift . liftBase
instance MonadThrow m => MonadThrow (GenT m) where
throwM =
Trans.lift . throwM
instance MonadCatch m => MonadCatch (GenT m) where
catch m onErr =
GenT $ \size seed ->
case Seed.split seed of
(sm, se) ->
(runGenT size sm m) `catch`
(runGenT size se . onErr)
instance MonadReader r m => MonadReader r (GenT m) where
ask =
Trans.lift ask
local f m =
mapGenT (local f) m
instance MonadState s m => MonadState s (GenT m) where
get =
Trans.lift get
put =
Trans.lift . put
state =
Trans.lift . state
instance MonadWriter w m => MonadWriter w (GenT m) where
writer =
Trans.lift . writer
tell =
Trans.lift . tell
listen =
mapGenT listen
pass =
mapGenT pass
instance MonadError e m => MonadError e (GenT m) where
throwError =
Trans.lift . throwError
catchError m onErr =
GenT $ \size seed ->
case Seed.split seed of
(sm, se) ->
(runGenT size sm m) `catchError`
(runGenT size se . onErr)
instance MonadResource m => MonadResource (GenT m) where
liftResourceT =
Trans.lift . liftResourceT
lift :: MonadGen m => Gen a -> m a
lift =
liftGen
generate :: MonadGen m => (Size -> Seed -> a) -> m a
generate f =
liftGen . GenT $ \size seed ->
pure (f size seed)
shrink :: MonadGen m => (a -> [a]) -> m a -> m a
shrink =
shrinkGen
prune :: MonadGen m => m a -> m a
prune =
pruneGen
sized :: MonadGen m => (Size -> m a) -> m a
sized f = do
f =<< generate (\size _ -> size)
resize :: MonadGen m => Size -> m a -> m a
resize size gen =
scale (const size) gen
scale :: MonadGen m => (Size -> Size) -> m a -> m a
scale =
scaleGen
small :: MonadGen m => m a -> m a
small =
scale golden
golden :: Size -> Size
golden x =
round (fromIntegral x * 0.61803398875 :: Double)
integral :: (MonadGen m, Integral a) => Range a -> m a
integral range =
shrink (Shrink.towards $ Range.origin range) (integral_ range)
integral_ :: (MonadGen m, Integral a) => Range a -> m a
integral_ range =
generate $ \size seed ->
let
(x, y) =
Range.bounds size range
in
fromInteger . fst $
Seed.nextInteger (toInteger x) (toInteger y) seed
int :: MonadGen m => Range Int -> m Int
int =
integral
int8 :: MonadGen m => Range Int8 -> m Int8
int8 =
integral
int16 :: MonadGen m => Range Int16 -> m Int16
int16 =
integral
int32 :: MonadGen m => Range Int32 -> m Int32
int32 =
integral
int64 :: MonadGen m => Range Int64 -> m Int64
int64 =
integral
word :: MonadGen m => Range Word -> m Word
word =
integral
word8 :: MonadGen m => Range Word8 -> m Word8
word8 =
integral
word16 :: MonadGen m => Range Word16 -> m Word16
word16 =
integral
word32 :: MonadGen m => Range Word32 -> m Word32
word32 =
integral
word64 :: MonadGen m => Range Word64 -> m Word64
word64 =
integral
realFloat :: (MonadGen m, RealFloat a) => Range a -> m a
realFloat range =
shrink (Shrink.towardsFloat $ Range.origin range) (realFrac_ range)
realFrac_ :: (MonadGen m, RealFrac a) => Range a -> m a
realFrac_ range =
generate $ \size seed ->
let
(x, y) =
Range.bounds size range
in
realToFrac . fst $
Seed.nextDouble (realToFrac x) (realToFrac y) seed
float :: MonadGen m => Range Float -> m Float
float =
realFloat
double :: MonadGen m => Range Double -> m Double
double =
realFloat
enum :: (MonadGen m, Enum a) => a -> a -> m a
enum lo hi =
fmap toEnum . integral $
Range.constant (fromEnum lo) (fromEnum hi)
enumBounded :: (MonadGen m, Enum a, Bounded a) => m a
enumBounded =
enum minBound maxBound
bool :: MonadGen m => m Bool
bool =
enumBounded
bool_ :: MonadGen m => m Bool
bool_ =
generate $ \_ seed ->
(/= 0) . fst $ Seed.nextInteger 0 1 seed
binit :: MonadGen m => m Char
binit =
enum '0' '1'
octit :: MonadGen m => m Char
octit =
enum '0' '7'
digit :: MonadGen m => m Char
digit =
enum '0' '9'
hexit :: MonadGen m => m Char
hexit =
element "0123456789aAbBcCdDeEfF"
lower :: MonadGen m => m Char
lower =
enum 'a' 'z'
upper :: MonadGen m => m Char
upper =
enum 'A' 'Z'
alpha :: MonadGen m => m Char
alpha =
element "abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
alphaNum :: MonadGen m => m Char
alphaNum =
element "abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
ascii :: MonadGen m => m Char
ascii =
enum '\0' '\127'
latin1 :: MonadGen m => m Char
latin1 =
enum '\0' '\255'
unicode :: MonadGen m => m Char
unicode =
filter (not . isSurrogate) unicodeAll
unicodeAll :: MonadGen m => m Char
unicodeAll =
enumBounded
isSurrogate :: Char -> Bool
isSurrogate x =
x >= '\55296' && x <= '\57343'
string :: MonadGen m => Range Int -> m Char -> m String
string =
list
text :: MonadGen m => Range Int -> m Char -> m Text
text range =
fmap Text.pack . string range
utf8 :: MonadGen m => Range Int -> m Char -> m ByteString
utf8 range =
fmap Text.encodeUtf8 . text range
bytes :: MonadGen m => Range Int -> m ByteString
bytes range =
fmap ByteString.pack $
choice [
list range . word8 $
Range.constant
(fromIntegral $ Char.ord 'a')
(fromIntegral $ Char.ord 'z')
, list range . word8 $
Range.constant minBound maxBound
]
constant :: MonadGen m => a -> m a
constant =
pure
element :: MonadGen m => [a] -> m a
element = \case
[] ->
error "Hedgehog.Gen.element: used with empty list"
xs -> do
n <- integral $ Range.constant 0 (length xs 1)
pure $ xs !! n
choice :: MonadGen m => [m a] -> m a
choice = \case
[] ->
error "Hedgehog.Gen.choice: used with empty list"
xs -> do
n <- integral $ Range.constant 0 (length xs 1)
xs !! n
frequency :: MonadGen m => [(Int, m a)] -> m a
frequency = \case
[] ->
error "Hedgehog.Gen.frequency: used with empty list"
xs0 -> do
let
pick n = \case
[] ->
error "Hedgehog.Gen.frequency/pick: used with empty list"
(k, x) : xs ->
if n <= k then
x
else
pick (n k) xs
total =
sum (fmap fst xs0)
n <- integral $ Range.constant 1 total
pick n xs0
recursive :: MonadGen m => ([m a] -> m a) -> [m a] -> [m a] -> m a
recursive f nonrec rec =
sized $ \n ->
if n <= 1 then
f nonrec
else
f $ nonrec ++ fmap small rec
discard :: MonadGen m => m a
discard =
liftGen mzero
ensure :: MonadGen m => (a -> Bool) -> m a -> m a
ensure p gen = do
x <- gen
if p x then
pure x
else
discard
filter :: MonadGen m => (a -> Bool) -> m a -> m a
filter p gen =
let
try k =
if k > 100 then
discard
else do
x <- scale (2 * k +) gen
if p x then
pure x
else
try (k + 1)
in
try 0
just :: MonadGen m => m (Maybe a) -> m a
just g = do
mx <- filter Maybe.isJust g
case mx of
Just x ->
pure x
Nothing ->
error "Hedgehog.Gen.just: internal error, unexpected Nothing"
maybe :: MonadGen m => m a -> m (Maybe a)
maybe gen =
sized $ \n ->
frequency [
(2, pure Nothing)
, (1 + fromIntegral n, Just <$> gen)
]
list :: MonadGen m => Range Int -> m a -> m [a]
list range gen =
sized $ \size ->
(traverse snd =<<) .
ensure (atLeast $ Range.lowerBound size range) .
shrink Shrink.list $ do
k <- integral_ range
replicateM k (freeze gen)
seq :: MonadGen m => Range Int -> m a -> m (Seq a)
seq range gen =
Seq.fromList <$> list range gen
nonEmpty :: MonadGen m => Range Int -> m a -> m (NonEmpty a)
nonEmpty range gen = do
xs <- list (fmap (max 1) range) gen
case xs of
[] ->
error "Hedgehog.Gen.nonEmpty: internal error, generated empty list"
_ ->
pure $ NonEmpty.fromList xs
set :: (MonadGen m, Ord a) => Range Int -> m a -> m (Set a)
set range gen =
fmap Map.keysSet . map range $ fmap (, ()) gen
map :: (MonadGen m, Ord k) => Range Int -> m (k, v) -> m (Map k v)
map range gen =
sized $ \size ->
ensure ((>= Range.lowerBound size range) . Map.size) .
fmap Map.fromList .
(sequence =<<) .
shrink Shrink.list $ do
k <- integral_ range
uniqueByKey k gen
uniqueByKey :: (MonadGen m, Ord k) => Int -> m (k, v) -> m [m (k, v)]
uniqueByKey n gen =
let
try k xs0 =
if k > 100 then
discard
else
replicateM n (freeze gen) >>= \kvs ->
case uniqueInsert n xs0 (fmap (first fst) kvs) of
Left xs ->
pure $ Map.elems xs
Right xs ->
try (k + 1) xs
in
try (0 :: Int) Map.empty
uniqueInsert :: Ord k => Int -> Map k v -> [(k, v)] -> Either (Map k v) (Map k v)
uniqueInsert n xs kvs0 =
if Map.size xs >= n then
Left xs
else
case kvs0 of
[] ->
Right xs
(k, v) : kvs ->
uniqueInsert n (Map.insertWith (\x _ -> x) k v xs) kvs
atLeast :: Int -> [a] -> Bool
atLeast n =
if n == 0 then
const True
else
not . null . drop (n 1)
data Subterms n a =
One a
| All (Vec n a)
deriving (Functor, Foldable, Traversable)
data Nat =
Z
| S Nat
data Vec n a where
Nil :: Vec 'Z a
(:.) :: a -> Vec n a -> Vec ('S n) a
infixr 5 :.
deriving instance Functor (Vec n)
deriving instance Foldable (Vec n)
deriving instance Traversable (Vec n)
freeze :: MonadGen m => m a -> m (a, m a)
freeze =
freezeGen
shrinkSubterms :: Subterms n a -> [Subterms n a]
shrinkSubterms = \case
One _ ->
[]
All xs ->
fmap One $ toList xs
genSubterms :: MonadGen m => Vec n (m a) -> m (Subterms n a)
genSubterms =
(sequence =<<) .
shrink shrinkSubterms .
fmap All .
mapM (fmap snd . freeze)
fromSubterms :: Applicative m => (Vec n a -> m a) -> Subterms n a -> m a
fromSubterms f = \case
One x ->
pure x
All xs ->
f xs
subtermMVec :: MonadGen m => Vec n (m a) -> (Vec n a -> m a) -> m a
subtermMVec gs f =
fromSubterms f =<< genSubterms gs
subtermM :: MonadGen m => m a -> (a -> m a) -> m a
subtermM gx f =
subtermMVec (gx :. Nil) $ \(x :. Nil) ->
f x
subterm :: MonadGen m => m a -> (a -> a) -> m a
subterm gx f =
subtermM gx $ \x ->
pure (f x)
subtermM2 :: MonadGen m => m a -> m a -> (a -> a -> m a) -> m a
subtermM2 gx gy f =
subtermMVec (gx :. gy :. Nil) $ \(x :. y :. Nil) ->
f x y
subterm2 :: MonadGen m => m a -> m a -> (a -> a -> a) -> m a
subterm2 gx gy f =
subtermM2 gx gy $ \x y ->
pure (f x y)
subtermM3 :: MonadGen m => m a -> m a -> m a -> (a -> a -> a -> m a) -> m a
subtermM3 gx gy gz f =
subtermMVec (gx :. gy :. gz :. Nil) $ \(x :. y :. z :. Nil) ->
f x y z
subterm3 :: MonadGen m => m a -> m a -> m a -> (a -> a -> a -> a) -> m a
subterm3 gx gy gz f =
subtermM3 gx gy gz $ \x y z ->
pure (f x y z)
subsequence :: MonadGen m => [a] -> m [a]
subsequence xs =
shrink Shrink.list $ filterM (const bool_) xs
shuffle :: MonadGen m => [a] -> m [a]
shuffle = \case
[] ->
pure []
xs0 -> do
n <- integral $ Range.constant 0 (length xs0 1)
case splitAt n xs0 of
(xs, y : ys) ->
(y :) <$> shuffle (xs ++ ys)
(_, []) ->
error "Hedgehog.Gen.shuffle: internal error, split generated empty list"
sample :: MonadIO m => Gen a -> m a
sample gen =
liftIO $
let
loop n =
if n <= 0 then
error "Hedgehog.Gen.sample: too many discards, could not generate a sample"
else do
seed <- Seed.random
case runIdentity . runMaybeT . runTree $ runGenT 30 seed gen of
Nothing ->
loop (n 1)
Just x ->
pure $ nodeValue x
in
loop (100 :: Int)
printWith :: (MonadIO m, Show a) => Size -> Seed -> Gen a -> m ()
printWith size seed gen =
liftIO $ do
let
Node x ss =
runIdentity . runTree $ renderNodes size seed gen
putStrLn "=== Outcome ==="
putStrLn x
putStrLn "=== Shrinks ==="
for_ ss $ \s ->
let
Node y _ =
runIdentity $ runTree s
in
putStrLn y
printTreeWith :: (MonadIO m, Show a) => Size -> Seed -> Gen a -> m ()
printTreeWith size seed gen = do
liftIO . putStr . runIdentity . Tree.render $ renderNodes size seed gen
print :: (MonadIO m, Show a) => Gen a -> m ()
print gen = do
seed <- liftIO Seed.random
printWith 30 seed gen
printTree :: (MonadIO m, Show a) => Gen a -> m ()
printTree gen = do
seed <- liftIO Seed.random
printTreeWith 30 seed gen
renderNodes :: (Monad m, Show a) => Size -> Seed -> Gen a -> Tree m String
renderNodes size seed =
fmap (Maybe.maybe "<discard>" show) . runDiscardEffect . runGenT size seed . lift