docs for utils; refactor tests; rewrite pnubSortBy

This commit is contained in:
fanghr 2022-05-16 18:11:14 +08:00
parent 67ff8f7f46
commit 28afcf2f65
3 changed files with 111 additions and 86 deletions

View file

@ -7,58 +7,66 @@ Tests for utility functions in 'Agora.Utils'.
-} -}
module Spec.Utils (tests) where module Spec.Utils (tests) where
import Agora.Utils (phalve, pmerge, pmsortOrd, tcmatch) import Agora.Utils (phalve, pmergeBy, pmsort)
import Data.List (sort) import Data.List (sort)
import Test.Tasty (TestTree) import Test.Tasty (TestTree)
import Test.Tasty.QuickCheck (testProperty) import Test.Tasty.QuickCheck (testProperty)
tests :: [TestTree] tests :: [TestTree]
tests = tests =
[ testProperty "Merge sort sorts a list properly" prop_msort_sorted [ testProperty "Merge sort sorts a list properly" prop_msortSorted
, testProperty "Two sorted lists are merged into one sorted list" prop_pmerge_sorted , testProperty "Two sorted lists are merged into one sorted list" prop_pmergeSorted
, testProperty "Split a list in half as expected" prop_halve_properly , testProperty "Split a list in half as expected" prop_halveProperly
] ]
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
prop_msort_sorted :: [Integer] -> Bool prop_msortSorted :: [Integer] -> Bool
prop_msort_sorted arr = sort arr == sorted prop_msortSorted arr = sorted == expected
where where
parr :: Term _ (PBuiltinList PInteger) -- Expected sorted list, using 'Data.List.sort'.
parr = pconstant arr expected :: [Integer]
expected = sort arr
--
psorted :: Term _ (PBuiltinList PInteger) psorted :: Term _ (PBuiltinList PInteger)
psorted = pmsortOrd # parr psorted = pmsort # pconstant arr
sorted :: [Integer] sorted :: [Integer]
sorted = plift psorted sorted = plift psorted
prop_pmerge_sorted :: ([Integer], [Integer]) -> Bool prop_pmergeSorted :: [Integer] -> [Integer] -> Bool
prop_pmerge_sorted (a, b) = merge sa sb == merged prop_pmergeSorted a b = merged == expected
where where
-- Sorted list a and b
sa = sort a sa = sort a
sb = sort b sb = sort b
-- Merge two lists which are assumed to be ordered.
merge :: [Integer] -> [Integer] -> [Integer]
merge xs [] = xs merge xs [] = xs
merge [] ys = ys merge [] ys = ys
merge sx@(x : xs) sy@(y : ys) merge sx@(x : xs) sy@(y : ys)
| x <= y = x : merge xs sy | x <= y = x : merge xs sy
| otherwise = y : merge sx ys | otherwise = y : merge sx ys
psa :: Term _ (PBuiltinList PInteger) expected :: [Integer]
psa = pconstant @(PBuiltinList PInteger) sa expected = merge sa sb
psb :: Term _ (PBuiltinList PInteger)
psb = pconstant @(PBuiltinList PInteger) sb --
pmerged :: Term _ (PBuiltinList PInteger) pmerged :: Term _ (PBuiltinList PInteger)
pmerged = pmerge # plam (#<) # psa # psb pmerged = pmergeBy # plam (#<) # pconstant sa # pconstant sb
merged :: [Integer] merged :: [Integer]
merged = plift pmerged merged = plift pmerged
prop_halve_properly :: [Integer] -> Bool prop_halveProperly :: [Integer] -> Bool
prop_halve_properly arr = halve arr == halved prop_halveProperly arr = halved == expected
where where
-- Halve a list.
halve :: [Integer] -> ([Integer], [Integer])
halve xs = go xs xs halve xs = go xs xs
where where
go xs [] = ([], xs) go xs [] = ([], xs)
@ -69,26 +77,16 @@ prop_halve_properly arr = halve arr == halved
in (x : first, last) in (x : first, last)
go [] _ = ([], []) go [] _ = ([], [])
parr :: Term _ (PBuiltinList PInteger) expected :: ([Integer], [Integer])
parr = pconstant arr expected = halve arr
ppairFst :: Term _ (PPair a b :--> a) --
ppairFst = phoistAcyclic $
plam $ \p -> unTermCont $ do
PPair x _ <- tcmatch p
return x
ppairSnd :: Term _ (PPair a b :--> b)
ppairSnd = phoistAcyclic $
plam $ \p -> unTermCont $ do
PPair _ y <- tcmatch p
return y
phalved :: Term _ (PPair (PBuiltinList PInteger) (PBuiltinList PInteger)) phalved :: Term _ (PPair (PBuiltinList PInteger) (PBuiltinList PInteger))
phalved = phalve # parr phalved = phalve # pconstant arr
halved :: ([Integer], [Integer]) halved :: ([Integer], [Integer])
halved = halved =
let f = plift $ ppairFst # phalved let f = plift $ pmatch phalved $ \(PPair x _) -> x
s = plift $ ppairSnd # phalved s = plift $ pmatch phalved $ \(PPair _ x) -> x
in (f, s) in (f, s)

View file

@ -21,7 +21,7 @@ import Agora.Utils (
anyOutput, anyOutput,
findTxOutByTxOutRef, findTxOutByTxOutRef,
getMintingPolicySymbol, getMintingPolicySymbol,
pisUniq, pisUniqBy,
psymbolValueOf, psymbolValueOf,
ptokenSpent, ptokenSpent,
ptxSignedBy, ptxSignedBy,
@ -171,7 +171,10 @@ proposalValidator proposal =
newSigs <- tclet $ pfield @"newCosigners" # r newSigs <- tclet $ pfield @"newCosigners" # r
tcassert "Cosigners are unique" $ tcassert "Cosigners are unique" $
pisUniq # phoistAcyclic (plam $ \(pfromData -> x) (pfromData -> y) -> x #< y) # newSigs pisUniqBy
# phoistAcyclic (plam (#==))
# phoistAcyclic (plam $ \(pfromData -> x) (pfromData -> y) -> x #< y)
# newSigs
tcassert "Signed by all new cosigners" $ tcassert "Signed by all new cosigners" $
pall # signedBy # newSigs pall # signedBy # newSigs

View file

@ -32,13 +32,14 @@ module Agora.Utils (
pisJust, pisJust,
ptokenSpent, ptokenSpent,
pkeysEqual, pkeysEqual,
pnub, pnubSortBy,
pisUniq, pisUniq,
pisUniqOrd, pisUniqBy,
pisDJust, pisDJust,
pisUTXOSpent, pisUTXOSpent,
pmsortBy,
pmsort, pmsort,
pmsortOrd, pnubSort,
-- * Functions which should (probably) not be upstreamed -- * Functions which should (probably) not be upstreamed
anyOutput, anyOutput,
@ -56,7 +57,7 @@ module Agora.Utils (
mustBePJust, mustBePJust,
mustBePDJust, mustBePDJust,
validatorHashToAddress, validatorHashToAddress,
pmerge, pmergeBy,
phalve, phalve,
) where ) where
@ -371,46 +372,67 @@ pkeysEqual = phoistAcyclic $
(plength # pks #== plength # qks) (plength # pks #== plength # qks)
( unTermCont $ do ( unTermCont $ do
let comp = phoistAcyclic $ plam $ \(pfromData -> x) (pfromData -> y) -> x #< y let comp = phoistAcyclic $ plam $ \(pfromData -> x) (pfromData -> y) -> x #< y
spks = pmsort # comp # pks spks = pmsortBy # comp # pks
sqks = pmsort # comp # qks sqks = pmsortBy # comp # qks
pure $ plistEquals # spks # sqks pure $ plistEquals # spks # sqks
) )
(pcon PFalse) (pcon PFalse)
-- | / O(nlogn) /. Clear out duplicates in a list. The order is not preserved. -- | / O(nlogn) /. Sort and remove dupicate elements in a list.
pnub :: forall list a (s :: S). (PEq a, PIsListLike list a) => Term s ((a :--> a :--> PBool) :--> list a :--> list a) pnubSortBy ::
pnub = phoistAcyclic $ forall list a (s :: S).
plam $ \comp xs -> unTermCont $ do (PIsListLike list a) =>
sorted <- tclet $ pmsort # comp # xs Term s ((a :--> a :--> PBool) :--> (a :--> a :--> PBool) :--> list a :--> list a)
pure $ pnubOrd # comp # sorted pnubSortBy = phoistAcyclic $
where plam $ \eq comp l -> pif (pnull # l) l $
pnubOrd = phoistAcyclic $ pfix #$ plam pnubOrd' unTermCont $ do
pnubOrd' self comp xs = sl <- tclet $ pmsortBy # comp # l
pif (pnull # xs) pnil $
unTermCont $ do
xh <- tclet $ phead # xs
xt <- tclet $ ptail # xs
pure $ let x = phead # sl
pif (pnull # xt) xs $ xs = ptail # sl
unTermCont $ do
xh' <- tclet $ phead # xt return $ pgo # eq # x # xs
pure $ where
pif pgo = phoistAcyclic pfix #$ plam pgo'
(xh #== xh') pgo' self eq seen l =
(self # comp # xt) pif (pnull # l) (psingleton # seen) $
(pcons # xh #$ self # comp # xt) unTermCont $ do
x <- tclet $ phead # l
xs <- tclet $ ptail # l
return $
pif
(eq # x # seen)
(self # eq # seen # xs)
(pcons # seen #$ self # eq # x # xs)
-- | Special version of 'pnubSortBy', which requires elements have 'POrd'.
pnubSort ::
forall list a (s :: S).
(PIsListLike list a, POrd a) =>
Term s (list a :--> list a)
pnubSort = phoistAcyclic $ pnubSortBy # eq # comp
where
eq = phoistAcyclic $ plam (#==)
comp = phoistAcyclic $ plam (#<)
-- | / O(nlogn) /. Check if a list contains no duplicates. -- | / O(nlogn) /. Check if a list contains no duplicates.
pisUniq :: forall list a (s :: S). (PEq a, PIsListLike list a) => Term s ((a :--> a :--> PBool) :--> list a :--> PBool) pisUniqBy ::
pisUniq = phoistAcyclic $ forall list a (s :: S).
plam $ \comp xs -> (PIsListLike list a) =>
let nubbed = pnub # comp # xs in plength # xs #== plength # nubbed Term s ((a :--> a :--> PBool) :--> (a :--> a :--> PBool) :--> list a :--> PBool)
pisUniqBy = phoistAcyclic $
plam $ \eq comp xs ->
let nubbed = pnubSortBy # eq # comp # xs
in plength # xs #== plength # nubbed
-- | List elements should have 'POrd' instance. -- | A special case of 'pisUniqBy' which requires elements have 'POrd' instance.
pisUniqOrd :: forall list a (s :: S). (POrd a, PIsListLike list a) => Term s (list a :--> PBool) pisUniq :: forall list a (s :: S). (POrd a, PIsListLike list a) => Term s (list a :--> PBool)
pisUniqOrd = phoistAcyclic $ pisUniq # plam (#<) pisUniq = phoistAcyclic $ pisUniqBy # eq # comp
where
eq = phoistAcyclic $ plam (#==)
comp = phoistAcyclic $ plam (#<)
-- | Yield True if a given PMaybeData is of form @'PDJust' _@. -- | Yield True if a given PMaybeData is of form @'PDJust' _@.
pisDJust :: Term s (PMaybeData a :--> PBool) pisDJust :: Term s (PMaybeData a :--> PBool)
@ -423,19 +445,17 @@ pisDJust = phoistAcyclic $
_ -> pconstant False _ -> pconstant False
) )
{- | Determines if a given UTXO is spent. -- | Determines if a given UTXO is spent.
TODO: no need to pass the whole TxInfo here.
-}
pisUTXOSpent :: Term s (PTxOutRef :--> PBuiltinList (PAsData PTxInInfo) :--> PBool) pisUTXOSpent :: Term s (PTxOutRef :--> PBuiltinList (PAsData PTxInInfo) :--> PBool)
pisUTXOSpent = phoistAcyclic $ pisUTXOSpent = phoistAcyclic $
plam $ \oref inputs -> P.do plam $ \oref inputs -> P.do
pisJust #$ pfindTxInByTxOutRef # oref # inputs pisJust #$ pfindTxInByTxOutRef # oref # inputs
-- | Merge two ordered lists together. -- | / O(n) /. Merge two lists which are assumed to be ordered, given a custom comparator.
pmerge :: (PIsListLike l a) => Term s ((a :--> a :--> PBool) :--> l a :--> l a :--> l a) pmergeBy :: (PIsListLike l a) => Term s ((a :--> a :--> PBool) :--> l a :--> l a :--> l a)
pmerge = phoistAcyclic $ pfix #$ plam pmerge' pmergeBy = phoistAcyclic $ pfix #$ plam pmergeBy'
where where
pmerge' self comp a b = pmergeBy' self comp a b =
pif (pnull # a) b $ pif (pnull # a) b $
pif (pnull # b) a $ pif (pnull # b) a $
unTermCont $ do unTermCont $ do
@ -450,20 +470,24 @@ pmerge = phoistAcyclic $ pfix #$ plam pmerge'
(pcons # ah #$ self # comp # at # b) (pcons # ah #$ self # comp # at # b)
(pcons # bh #$ self # comp # a # bt) (pcons # bh #$ self # comp # a # bt)
-- | / O(nlogn) /. Merge sort, bottom-up version. {- | / O(nlogn) /. Merge sort, bottom-up version, given a custom comparator.
pmsort :: (PIsListLike l a) => Term s ((a :--> a :--> PBool) :--> l a :--> l a)
pmsort = phoistAcyclic $ pfix #$ plam pmsort' Elements are arranged from lowest to highest,
keeping duplicates in the order they appeared in the input.
-}
pmsortBy :: (PIsListLike l a) => Term s ((a :--> a :--> PBool) :--> l a :--> l a)
pmsortBy = phoistAcyclic $ pfix #$ plam pmsortBy'
where where
pmsort' self comp xs = pif (pnull # xs) pnil $ pmsortBy' self comp xs = pif (pnull # xs) pnil $
pif (pnull #$ ptail # xs) xs $ pif (pnull #$ ptail # xs) xs $
pmatch (phalve # xs) $ \(PPair fh sh) -> pmatch (phalve # xs) $ \(PPair fh sh) ->
let sfh = self # comp # fh let sfh = self # comp # fh
ssh = self # comp # sh ssh = self # comp # sh
in pmerge # comp # sfh # ssh in pmergeBy # comp # sfh # ssh
-- | Required list elements have 'POrd' instance. -- | A special case of 'pmsortBy' which requires elements have 'POrd' instance.
pmsortOrd :: (POrd a, PIsListLike l a) => Term s (l a :--> l a) pmsort :: (POrd a, PIsListLike l a) => Term s (l a :--> l a)
pmsortOrd = phoistAcyclic $ pmsort # comp pmsort = phoistAcyclic $ pmsortBy # comp
where where
comp = phoistAcyclic $ plam (#<) comp = phoistAcyclic $ plam (#<)