{-# language GADTs, RankNTypes #-}
{-# language FlexibleContexts, DefaultSignatures #-}
{-# language TypeOperators #-}
{-# language LambdaCase #-}
{-# language EmptyCase #-}
module Hedgehog.Function.Internal where
import Control.Monad.Trans.Maybe (MaybeT(..))
import Data.Bifunctor (first)
import Data.Functor.Contravariant (Contravariant(..))
import Data.Functor.Contravariant.Divisible (Divisible(..), Decidable(..))
import Data.Functor.Identity (Identity(..))
import Data.Int (Int8, Int16, Int32, Int64)
import Data.Maybe (fromJust)
import Data.Void (Void, absurd)
import Data.Word (Word8, Word64)
import Hedgehog.Internal.Gen (GenT(..), Gen, runGenT)
import Hedgehog.Internal.Seed (Seed(..))
import Hedgehog.Internal.Tree (TreeT(..), NodeT(..))
import Hedgehog.Internal.Property (PropertyT, forAll)
import GHC.Generics
import qualified Hedgehog.Internal.Tree as Tree
infixr 5 :->
data a :-> c where
Unit :: c -> () :-> c
Nil :: a :-> c
Pair :: a :-> b :-> c -> (a, b) :-> c
Sum :: a :-> c -> b :-> c -> Either a b :-> c
Map :: (a -> b) -> (b -> a) -> b :-> c -> a :-> c
instance Functor ((:->) r) where
fmap f (Unit c) = Unit $ f c
fmap _ Nil = Nil
fmap f (Pair a) = Pair $ fmap (fmap f) a
fmap f (Sum a b) = Sum (fmap f a) (fmap f b)
fmap f (Map a b c) = Map a b (fmap f c)
table :: a :-> c -> [(a, c)]
table (Unit c) = [((), c)]
table Nil = []
table (Pair f) = do
(a, bc) <- table f
(b, c) <- table bc
pure ((a, b), c)
table (Sum a b) =
[(Left x, c) | (x, c) <- table a] ++
[(Right x, c) | (x, c) <- table b]
table (Map _ g a) = first g <$> table a
class GArg a where
gbuild' :: (a x -> c) -> a x :-> c
gbuild :: (Generic a, GArg (Rep a)) => (a -> c) -> a :-> c
gbuild = gvia from to
class Arg a where
build :: (a -> c) -> a :-> c
default build :: (Generic a, GArg (Rep a)) => (a -> c) -> a :-> c
build = gbuild
variant :: Word64 -> GenT m b -> GenT m b
variant n (GenT f) = GenT $ \sz sd -> f sz (sd { seedValue = seedValue sd + n})
variant' :: Word64 -> CoGenT m b -> CoGenT m b
variant' n (CoGenT f) =
CoGenT $ \a -> variant n . f a
class GVary a where
gvary' :: CoGenT m (a x)
instance GVary V1 where
gvary' = conquer
instance GVary U1 where
gvary' = conquer
instance (GVary a, GVary b) => GVary (a :+: b) where
gvary' =
choose
(\case; L1 a -> Left a; R1 a -> Right a)
(variant' 0 gvary')
(variant' 1 gvary')
instance (GVary a, GVary b) => GVary (a :*: b) where
gvary' =
divide
(\(a :*: b) -> (a, b))
(variant' 0 gvary')
(variant' 1 gvary')
instance GVary c => GVary (M1 a b c) where
gvary' = contramap unM1 gvary'
instance Vary b => GVary (K1 a b) where
gvary' = contramap unK1 vary
gvary :: (Generic a, GVary (Rep a)) => CoGenT m a
gvary = CoGenT $ \a -> applyCoGenT gvary' (from a)
class Vary a where
vary :: CoGenT m a
default vary :: (Generic a, GVary (Rep a)) => CoGenT m a
vary = gvary
varyIntegral :: Integral a => CoGenT m a
varyIntegral = CoGenT $ variant . fromIntegral
newtype CoGenT m a = CoGenT { applyCoGenT :: forall b. a -> GenT m b -> GenT m b }
type CoGen = CoGenT Identity
instance Contravariant (CoGenT m) where
contramap f (CoGenT g) = CoGenT (g . f)
instance Divisible (CoGenT m) where
divide f (CoGenT gb) (CoGenT gc) =
CoGenT $ \a ->
let (b, c) = f a in gc c . gb b
conquer = CoGenT $ const id
instance Decidable (CoGenT m) where
choose f (CoGenT gb) (CoGenT gc) =
CoGenT $ \a ->
case f a of
Left b -> gb b . variant 0
Right c -> gc c . variant 1
lose f = CoGenT $ \a -> absurd (f a)
instance (Show a, Show b) => Show (a :-> b) where
show = show . table
apply' :: a :-> b -> a -> Maybe b
apply' (Unit c) () = Just c
apply' Nil _ = Nothing
apply' (Pair f) (a, b) = do
f' <- apply' f a
apply' f' b
apply' (Sum f _) (Left a) = apply' f a
apply' (Sum _ g) (Right a) = apply' g a
apply' (Map f _ g) a = apply' g (f a)
unsafeApply :: a :-> b -> a -> b
unsafeApply f = fromJust . apply' f
data Fn a b = Fn b (a :-> TreeT (MaybeT Identity) b)
unsafeFromTree :: Functor m => TreeT (MaybeT m) a -> m a
unsafeFromTree =
fmap (maybe (error "empty generator in function") nodeValue) .
runMaybeT .
runTreeT
instance (Show a, Show b) => Show (Fn a b) where
show (Fn b a) =
case table a of
[] -> "_ -> " ++ show b
ta -> showTable ta ++ "_ -> " ++ show b
where
showTable :: (Show a, Show b) => [(a, TreeT (MaybeT Identity) b)] -> String
showTable [] = "<empty function>\n"
showTable (x : xs) = unlines (showCase <$> x : xs)
where
showCase (lhs, rhs) = show lhs ++ " -> " ++ show (runIdentity $ unsafeFromTree rhs)
shrinkFn :: (b -> [b]) -> a :-> b -> [a :-> b]
shrinkFn shr (Unit a) = Unit <$> shr a
shrinkFn _ Nil = []
shrinkFn shr (Pair f) =
(\case; Nil -> Nil; a -> Pair a) <$> shrinkFn (shrinkFn shr) f
shrinkFn shr (Sum a b) =
fmap (\case; Sum Nil Nil -> Nil; x -> x) $
[ Sum a Nil | notNil b ] ++
[ Sum Nil b | notNil a ] ++
fmap (`Sum` b) (shrinkFn shr a) ++
fmap (a `Sum`) (shrinkFn shr b)
where
notNil Nil = False
notNil _ = True
shrinkFn shr (Map f g a) = (\case; Nil -> Nil; x -> Map f g x) <$> shrinkFn shr a
shrinkTree :: Monad m => TreeT (MaybeT m) a -> m [TreeT (MaybeT m) a]
shrinkTree (Tree.TreeT m) = do
a <- runMaybeT m
case a of
Nothing -> pure []
Just (Tree.NodeT _ cs) -> pure cs
apply :: Fn a b -> a -> b
apply (Fn b f) = maybe b (runIdentity . unsafeFromTree) . apply' f
fnWith :: Arg a => CoGen a -> Gen b -> Gen (Fn a b)
fnWith cg gb =
Fn <$>
gb <*>
genFn (\a -> applyCoGenT cg a gb)
where
genFn :: Arg a => (a -> Gen b) -> Gen (a :-> TreeT (MaybeT Identity) b)
genFn g =
GenT $ \sz sd ->
Tree.unfold (shrinkFn $ runIdentity . shrinkTree) .
fmap (runGenT sz sd) $ build g
fn :: (Arg a, Vary a) => Gen b -> Gen (Fn a b)
fn = fnWith vary
forAllFn :: (Show a, Show b, Monad m) => Gen (Fn a b) -> PropertyT m (a -> b)
forAllFn = fmap apply . forAll
instance Vary ()
instance (Vary a, Vary b) => Vary (Either a b)
instance (Vary a, Vary b) => Vary (a, b)
instance Vary Void
instance Vary Bool
instance Vary Ordering
instance Vary a => Vary (Maybe a)
instance Vary a => Vary [a]
instance Vary Int8 where; vary = varyIntegral
instance Vary Int16 where; vary = varyIntegral
instance Vary Int32 where; vary = varyIntegral
instance Vary Int64 where; vary = varyIntegral
instance Vary Int where; vary = varyIntegral
instance Vary Integer where; vary = varyIntegral
instance Vary Word8 where; vary = varyIntegral
via :: Arg b => (a -> b) -> (b -> a) -> (a -> c) -> a :-> c
via a b f = Map a b . build $ f . b
instance Arg Void where
build _ = Nil
instance Arg () where
build f = Unit $ f ()
instance (Arg a, Arg b) => Arg (a, b) where
build f = Pair . build $ \a -> build $ \b -> f (a, b)
instance (Arg a, Arg b) => Arg (Either a b) where
build f = Sum (build $ f . Left) (build $ f . Right)
gvia :: GArg b => (a -> b x) -> (b x -> a) -> (a -> c) -> a :-> c
gvia a b f = Map a b . gbuild' $ f . b
instance GArg V1 where
gbuild' _ = Nil
instance GArg U1 where
gbuild' f = Map (\U1 -> ()) (\() -> U1) (Unit $ f U1)
instance (GArg a, GArg b) => GArg (a :*: b) where
gbuild' f = Map fromPair toPair $ Pair . gbuild' $ \a -> gbuild' $ \b -> f (a :*: b)
where
fromPair (a :*: b) = (a, b)
toPair (a, b) = (a :*: b)
instance (GArg a, GArg b) => GArg (a :+: b) where
gbuild' f = Map fromSum toSum $ Sum (gbuild' $ f . L1) (gbuild' $ f . R1)
where
fromSum = \case; L1 a -> Left a; R1 a -> Right a
toSum = either L1 R1
instance GArg c => GArg (M1 a b c) where
gbuild' = gvia unM1 M1
instance Arg b => GArg (K1 a b) where
gbuild' f = Map unK1 K1 . build $ f . K1
buildIntegral :: (Arg a, Integral a) => (a -> c) -> (a :-> c)
buildIntegral = via toBits fromBits
where
toBits :: Integral a => a -> (Bool, [Bool])
toBits n
| n >= 0 = (True, go n)
| otherwise = (False, go $ -n - 1)
where
go 0 = []
go m =
let
(q, r) = quotRem m 2
in
(r == 1) : go q
fromBits :: Integral a => (Bool, [Bool]) -> a
fromBits (pos, bts)
| pos = go bts
| otherwise = negate $ go bts + 1
where
go [] = 0
go (x:xs) = (if x then 1 else 0) + 2 * go xs
instance Arg Bool
instance Arg Ordering
instance Arg a => Arg (Maybe a)
instance Arg a => Arg [a]
instance Arg Int8 where; build = buildIntegral
instance Arg Int16 where; build = buildIntegral
instance Arg Int32 where; build = buildIntegral
instance Arg Int64 where; build = buildIntegral
instance Arg Int where; build = buildIntegral
instance Arg Integer where; build = buildIntegral