packages feed

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