borsh-0.1.0: test/Test/Codec/Borsh/Util/SOP.hs
{-# LANGUAGE PatternSynonyms #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Test.Codec.Borsh.Util.SOP (
-- * Mapping
mapHeadNP
, mapTailNP
, mapHeadSOP
, mapTailSOP
-- * Misc
, lengthNP
-- * Generation
, arbitraryNP
, arbitrarySOP
) where
import Data.SOP
import Test.QuickCheck hiding (pattern Fn)
import Optics.Core
import Test.Codec.Borsh.Util.QuickCheck
{-------------------------------------------------------------------------------
Mapping
-------------------------------------------------------------------------------}
mapHeadNP ::
Functor m
=> (f x -> m (f y))
-> NP f (x ': xs) -> m (NP f (y ': xs))
mapHeadNP f (x :* xs) = fmap (:* xs) (f x)
mapTailNP ::
Functor m
=> (NP f xs -> m (NP f ys))
-> NP f (z : xs) -> m (NP f (z : ys))
mapTailNP f (z :* xs) = fmap (z :*) (f xs)
mapHeadSOP ::
Applicative m
=> (NP f xs -> m (NP f ys))
-> SOP f (xs ': xss) -> m (SOP f (ys ': xss))
mapHeadSOP f (SOP (Z x)) = SOP . Z <$> f x
mapHeadSOP _ (SOP (S s)) = pure $ SOP (S s)
mapTailSOP ::
Applicative m
=> (SOP f xss -> m (SOP f yss))
-> SOP f (zs : xss) -> m (SOP f (zs : yss))
mapTailSOP _ (SOP (Z x)) = pure $ SOP (Z x)
mapTailSOP f (SOP (S s)) = SOP . S . unSOP <$> f (SOP s)
{-------------------------------------------------------------------------------
Misc
-------------------------------------------------------------------------------}
lengthNP :: forall f xs. SListI xs => NP f xs -> Int
lengthNP _ = lengthSList (Proxy @xs)
{-------------------------------------------------------------------------------
Lenses
-------------------------------------------------------------------------------}
newtype LensNP xs a = LensNP (Lens' (NP I xs) a)
lensHeadNP :: LensNP (x : xs) x
lensHeadNP = LensNP $
lens
(unI . hd)
(\(_ :* xs) x -> I x :* xs)
shiftLensNP :: LensNP xs a -> LensNP (x : xs) a
shiftLensNP (LensNP l) = LensNP $
lens
(view l . tl)
(\(x :* xs) a -> x :* set l a xs)
lensesNP :: forall xs. SListI xs => NP (LensNP xs) xs
lensesNP =
case sList :: SList xs of
SNil -> Nil
SCons -> lensHeadNP :* hmap shiftLensNP lensesNP
{-------------------------------------------------------------------------------
Compositional generation
-------------------------------------------------------------------------------}
arbitraryNP :: SListI xs => NP Gen xs -> Gen (NP I xs)
arbitraryNP = hsequence
-- | Auxiliary to 'arbitrarySOP'
--
-- Post-condition: the result list will have as many entries as the POP.
arbitrarySOP' :: forall xss. All SListI xss => POP Gen xss -> [Gen (SOP I xss)]
arbitrarySOP' =
hcollapse
. hczipWith
(Proxy @SListI) (\(Fn inj) -> K . aux (SOP . unK . inj) )
(injections @xss @(NP I))
. unPOP
where
aux :: SListI xs => (NP I xs -> SOP I xss) -> NP Gen xs -> Gen (SOP I xss)
aux inj = fmap inj . arbitraryNP
-- | Generate arbitrary SOP
--
-- The restriction to non-empty SOPs ensures the call to 'oneof' will not fail.
arbitrarySOP ::
All SListI (xs ': xss)
=> POP Gen (xs ': xss) -> Gen (SOP I (xs ': xss))
arbitrarySOP = oneof . arbitrarySOP'
{-------------------------------------------------------------------------------
Compositional shrinking
-------------------------------------------------------------------------------}
shrinkNP :: forall xs. SListI xs => NP Shrinker xs -> Shrinker (NP I xs)
shrinkNP =
mconcat
. hcollapse
. hzipWith (\(LensNP l) (Shrinker f) -> K $ Shrinker $ aux l f) lensesNP
where
aux :: Lens' (NP I xs) a -> (a -> [a]) -> NP I xs -> [NP I xs]
aux l f xs = [ set l a' xs | a' <- f (view l xs) ]
shrinkSOP :: forall xss.
All SListI xss
=> POP Shrinker xss -> Shrinker (SOP I xss)
shrinkSOP = \(POP sss) -> Shrinker $ \(SOP xss) ->
hcollapse
$ hczipWith3
(Proxy @SListI)
(\ss (Fn inj) xs -> K $ aux (shrinkNP ss) (SOP . unK . inj) xs)
sss
(injections @xss @(NP I))
xss
where
aux :: Shrinker (NP I a) -> (NP I a -> SOP I xss) -> NP I a -> [SOP I xss]
aux (Shrinker f) inj = map inj . f
{-------------------------------------------------------------------------------
Arbitrary instances
-------------------------------------------------------------------------------}
instance All Arbitrary xs => Arbitrary (NP I xs) where
arbitrary = arbitraryNP $ hcpure (Proxy @Arbitrary) arbitrary
shrink = runShrinker $ shrinkNP $ hcpure (Proxy @Arbitrary) shrinker
instance ( SListI (xs ': xss)
, All SListI xss
, All2 Arbitrary (xs ': xss)
) => Arbitrary (SOP I (xs ': xss)) where
arbitrary = arbitrarySOP $ hcpure (Proxy @Arbitrary) arbitrary
shrink = runShrinker $ shrinkSOP $ hcpure (Proxy @Arbitrary) shrinker