{-# OPTIONS_HADDOCK not-home #-}
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DoAndIfThenElse #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Hedgehog.Internal.Gen (
Gen
, GenT(..)
, MonadGen(..)
, generalize
, 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
, filterT
, just
, justT
, maybe
, list
, seq
, nonEmpty
, set
, map
, freeze
, subterm
, subtermM
, subterm2
, subtermM2
, subterm3
, subtermM3
, subsequence
, shuffle
, sample
, print
, printTree
, printWith
, printTreeWith
, renderTree
, runGenT
, evalGen
, evalGenT
, mapGenT
, generate
, toTree
, toTreeMaybeT
, fromTree
, fromTreeT
, fromTreeMaybeT
, runDiscardEffect
, runDiscardEffectT
, golden
, atLeast
, isSurrogate
, isNoncharacter
, Vec(..)
, Nat(..)
, subtermMVec
) where
import Control.Applicative (Alternative(..),liftA2)
import Control.Monad (MonadPlus(..), filterM, replicateM, join)
import Control.Monad.Base (MonadBase(..))
import Control.Monad.Catch (MonadThrow(..), MonadCatch(..))
import Control.Monad.Error.Class (MonadError(..))
import Control.Monad.Fail (MonadFail (..))
import qualified Control.Monad.Fail as Fail
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Morph (MFunctor(..), MMonad(..))
import qualified Control.Monad.Morph as Morph
import Control.Monad.Primitive (PrimMonad(..))
import Control.Monad.Reader.Class (MonadReader(..))
import Control.Monad.State.Class (MonadState(..))
import Control.Monad.Trans.Class (MonadTrans(..))
import Control.Monad.Trans.Except (ExceptT(..))
import Control.Monad.Trans.Identity (IdentityT(..))
import Control.Monad.Trans.Maybe (MaybeT(..))
import Control.Monad.Trans.Reader (ReaderT(..))
import Control.Monad.Trans.Resource (MonadResource(..))
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 Control.Monad.Zip (MonadZip(..))
import Data.Bifunctor (first)
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.Semigroup (Semigroup)
import qualified Data.Semigroup as Semigroup
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 (MonadTransDistributive(..))
import Hedgehog.Internal.Seed (Seed)
import qualified Hedgehog.Internal.Seed as Seed
import qualified Hedgehog.Internal.Shrink as Shrink
import Hedgehog.Internal.Tree (Tree, TreeT(..), NodeT(..))
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 {
unGenT :: Size -> Seed -> TreeT (MaybeT m) a
}
runGenT :: Size -> Seed -> GenT m a -> TreeT (MaybeT m) a
runGenT size seed (GenT m) =
m size seed
evalGen :: Size -> Seed -> Gen a -> Maybe (Tree a)
evalGen size seed =
fmap (fmap Maybe.fromJust) .
Tree.filter Maybe.isJust .
evalGenT size seed
evalGenT :: Monad m => Size -> Seed -> GenT m a -> TreeT m (Maybe a)
evalGenT size seed =
runDiscardEffectT .
runGenT size seed
mapGenT :: (TreeT (MaybeT m) a -> TreeT (MaybeT n) b) -> GenT m a -> GenT n b
mapGenT f gen =
GenT $ \size seed ->
f (runGenT size seed gen)
fromTree :: MonadGen m => Tree a -> m a
fromTree =
fromTreeT .
hoist (Morph.generalize)
fromTreeT :: MonadGen m => TreeT (GenBase m) a -> m a
fromTreeT x =
fromTreeMaybeT $
hoist (MaybeT . fmap Just) x
fromTreeMaybeT :: MonadGen m => TreeT (MaybeT (GenBase m)) a -> m a
fromTreeMaybeT x =
fromGenT . GenT $ \_ _ ->
x
toTree :: forall m a. (MonadGen m, GenBase m ~ Identity) => m a -> m (Tree a)
toTree =
withGenT $ mapGenT (Maybe.maybe empty pure . runDiscardEffect)
toTreeMaybeT :: MonadGen m => m a -> m (TreeT (MaybeT (GenBase m)) a)
toTreeMaybeT =
withGenT $ mapGenT pure
runDiscardEffect :: TreeT (MaybeT Identity) a -> Maybe (Tree a)
runDiscardEffect =
fmap (fmap Maybe.fromJust) .
Tree.filter Maybe.isJust .
runDiscardEffectT
runDiscardEffectT :: Monad m => TreeT (MaybeT m) a -> TreeT m (Maybe a)
runDiscardEffectT =
runMaybeT .
distributeT
generalize :: Monad m => Gen a -> GenT m a
generalize =
hoist Morph.generalize
class (Monad m, Monad (GenBase m)) => MonadGen m where
type GenBase m :: (* -> *)
toGenT :: m a -> GenT (GenBase m) a
fromGenT :: GenT (GenBase m) a -> m a
withGenT :: (MonadGen m, MonadGen n) => (GenT (GenBase m) a -> GenT (GenBase n) b) -> m a -> n b
withGenT f =
fromGenT . f . toGenT
instance Monad m => MonadGen (GenT m) where
type GenBase (GenT m) =
m
toGenT =
id
fromGenT =
id
instance MonadGen m => MonadGen (IdentityT m) where
type GenBase (IdentityT m) =
IdentityT (GenBase m)
toGenT =
distributeT . hoist toGenT
fromGenT =
hoist fromGenT . distributeT
instance MonadGen m => MonadGen (MaybeT m) where
type GenBase (MaybeT m) =
MaybeT (GenBase m)
toGenT =
distributeT . hoist toGenT
fromGenT =
hoist fromGenT . distributeT
instance MonadGen m => MonadGen (ExceptT x m) where
type GenBase (ExceptT x m) =
ExceptT x (GenBase m)
toGenT =
distributeT . hoist toGenT
fromGenT =
hoist fromGenT . distributeT
instance MonadGen m => MonadGen (ReaderT r m) where
type GenBase (ReaderT r m) =
ReaderT r (GenBase m)
toGenT =
distributeT . hoist toGenT
fromGenT =
hoist fromGenT . distributeT
instance (MonadGen m, Monoid w) => MonadGen (Lazy.WriterT w m) where
type GenBase (Lazy.WriterT w m) =
Lazy.WriterT w (GenBase m)
toGenT =
distributeT . hoist toGenT
fromGenT =
hoist fromGenT . distributeT
instance (MonadGen m, Monoid w) => MonadGen (Strict.WriterT w m) where
type GenBase (Strict.WriterT w m) =
Strict.WriterT w (GenBase m)
toGenT =
distributeT . hoist toGenT
fromGenT =
hoist fromGenT . distributeT
instance (Monad m, Semigroup a) => Semigroup (GenT m a) where
(<>) =
liftA2 (Semigroup.<>)
instance (Monad m, Monoid a) => Monoid (GenT m a) where
mappend =
liftA2 mappend
mempty =
return mempty
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 =
fromTreeMaybeT . pure
(<*>) f m =
GenT $ \ size seed ->
case Seed.split seed of
(sf, sm) ->
uncurry ($) <$>
runGenT size sf f `mzip`
runGenT size sm m
instance Monad m => Monad (GenT m) where
return =
pure
(>>=) m k =
GenT $ \size seed ->
case Seed.split seed of
(sk, sm) ->
runGenT size sk . k =<<
runGenT size sm m
#if MIN_VERSION_base(4,13,0)
#else
fail =
Fail.fail
#endif
instance Monad m => MonadFail (GenT m) where
fail =
error
instance Monad m => Alternative (GenT m) where
empty =
mzero
(<|>) =
mplus
instance Monad m => MonadPlus (GenT m) where
mzero =
fromTreeMaybeT 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 =
fromTreeMaybeT . lift . lift
instance MFunctor GenT where
hoist f =
mapGenT (hoist (hoist f))
embedMaybeT ::
MonadTrans t
=> Monad n
=> Monad (t (MaybeT n))
=> (forall a. m a -> t (MaybeT n) a)
-> MaybeT m b
-> t (MaybeT n) b
embedMaybeT f m =
lift . MaybeT . pure =<< f (runMaybeT m)
embedTreeMaybeT ::
Monad n
=> (forall a. m a -> TreeT (MaybeT n) a)
-> TreeT (MaybeT m) b
-> TreeT (MaybeT n) b
embedTreeMaybeT f tree_ =
embed (embedMaybeT f) tree_
embedGenT ::
Monad n
=> (forall a. m a -> GenT n a)
-> GenT m b
-> GenT n b
embedGenT f gen =
GenT $ \size seed ->
case Seed.split seed of
(sf, sg) ->
(runGenT size sf . f) `embedTreeMaybeT`
(runGenT size sg gen)
instance MMonad GenT where
embed =
embedGenT
distributeGenT :: Transformer t GenT m => GenT (t m) a -> t (GenT m) a
distributeGenT x =
join . lift . GenT $ \size seed ->
pure . hoist fromTreeMaybeT . distributeT . hoist distributeT $ runGenT size seed x
instance MonadTransDistributive GenT where
type Transformer t GenT m = (
Monad (t (GenT m))
, Transformer t MaybeT m
, Transformer t TreeT (MaybeT m)
)
distributeT =
distributeGenT
instance PrimMonad m => PrimMonad (GenT m) where
type PrimState (GenT m) =
PrimState m
primitive =
lift . primitive
instance MonadIO m => MonadIO (GenT m) where
liftIO =
lift . liftIO
instance MonadBase b m => MonadBase b (GenT m) where
liftBase =
lift . liftBase
instance MonadThrow m => MonadThrow (GenT m) where
throwM =
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 =
lift ask
local f m =
mapGenT (local f) m
instance MonadState s m => MonadState s (GenT m) where
get =
lift get
put =
lift . put
state =
lift . state
instance MonadWriter w m => MonadWriter w (GenT m) where
writer =
lift . writer
tell =
lift . tell
listen =
undefined
pass =
undefined
instance MonadError e m => MonadError e (GenT m) where
throwError =
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 =
lift . liftResourceT
generate :: MonadGen m => (Size -> Seed -> a) -> m a
generate f =
fromGenT . GenT $ \size seed ->
pure (f size seed)
shrink :: MonadGen m => (a -> [a]) -> m a -> m a
shrink f =
withGenT $ mapGenT (Tree.expand f)
prune :: MonadGen m => m a -> m a
prune =
withGenT $ mapGenT (Tree.prune 0)
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 f =
withGenT $ \gen ->
GenT $ \size0 seed ->
let
size =
f size0
in
if size < 0 then
error "Hedgehog.Gen.scale: negative size"
else
runGenT size seed gen
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 =
let
s1 =
(55296, enum '\0' '\55295')
s2 =
(8190, enum '\57344' '\65533')
s3 =
(1048576, enum '\65536' '\1114111')
in
frequency [s1, s2, s3]
unicodeAll :: MonadGen m => m Char
unicodeAll =
enumBounded
isSurrogate :: Char -> Bool
isSurrogate x =
x >= '\55296' && x <= '\57343'
isNoncharacter :: Char -> Bool
isNoncharacter x =
x == '\65534' || x == '\65535'
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 =
fromGenT empty
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, GenBase m ~ Identity) => (a -> Bool) -> m a -> m a
filter p gen0 =
let
try k =
if k > 100 then
discard
else do
(x, gen) <- freeze $ scale (2 * k +) gen0
if p x then
withGenT (mapGenT (Tree.filterMaybeT p)) gen
else
try (k + 1)
in
try 0
filterT :: MonadGen m => (a -> Bool) -> m a -> m a
filterT p gen0 =
let
try k =
if k > 100 then
discard
else do
(x, gen) <- freeze $ scale (2 * k +) gen0
if p x then
withGenT (mapGenT (Tree.filterT p)) gen
else
try (k + 1)
in
try 0
just :: (MonadGen m, GenBase m ~ Identity) => 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"
justT :: MonadGen m => m (Maybe a) -> m a
justT g = do
mx <- filterT 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 =
let
interleave =
(interleaveTreeT . nodeValue =<<)
in
sized $ \size ->
ensure (atLeast $ Range.lowerBound size range) .
withGenT (mapGenT (TreeT . interleave . runTreeT)) $ do
n <- integral_ range
replicateM n (toTreeMaybeT gen)
interleaveTreeT :: Monad m => [TreeT m a] -> m (NodeT m [a])
interleaveTreeT =
fmap Tree.interleave . traverse runTreeT
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 =
withGenT $ \gen ->
GenT $ \size seed -> do
mx <- lift . lift . runMaybeT . runTreeT $ runGenT size seed gen
case mx of
Nothing ->
empty
Just (NodeT x xs) ->
pure (x, fromGenT . fromTreeMaybeT . Tree.fromNodeT $ NodeT x xs)
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 evalGen 30 seed gen of
Nothing ->
loop (n - 1)
Just x ->
pure $ Tree.treeValue x
in
loop (100 :: Int)
print :: (MonadIO m, Show a) => Gen a -> m ()
print gen = do
seed <- liftIO Seed.random
printWith 30 seed gen
printWith :: (MonadIO m, Show a) => Size -> Seed -> Gen a -> m ()
printWith size seed gen =
liftIO $ do
case evalGen size seed gen of
Nothing -> do
putStrLn "=== Outcome ==="
putStrLn "<discard>"
Just tree_ -> do
let
NodeT x ss =
runIdentity (runTreeT tree_)
putStrLn "=== Outcome ==="
putStrLn (show x)
putStrLn "=== Shrinks ==="
for_ ss $ \s ->
let
NodeT y _ =
runIdentity $ runTreeT s
in
putStrLn (show y)
printTree :: (MonadIO m, Show a) => Gen a -> m ()
printTree gen = do
seed <- liftIO Seed.random
printTreeWith 30 seed gen
printTreeWith :: (MonadIO m, Show a) => Size -> Seed -> Gen a -> m ()
printTreeWith size seed gen = do
liftIO . putStr $
renderTree size seed gen
renderTree :: Show a => Size -> Seed -> Gen a -> String
renderTree size seed gen =
case evalGen size seed gen of
Nothing ->
"<discard>"
Just x ->
Tree.render (fmap show x)