Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PlutusTx Map backed by Data #5927

Merged
merged 44 commits into from
May 21, 2024
Merged
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c03986f
Add AssocList backed by Data
ana-pantilie Apr 29, 2024
0fcaac8
Fix build
ana-pantilie Apr 29, 2024
360474d
Fix AssocList union
ana-pantilie Apr 29, 2024
a730d6b
Add unionWith property test
ana-pantilie Apr 29, 2024
0d75e65
Clean-up
ana-pantilie Apr 29, 2024
ec6bccd
Fix performance bug
ana-pantilie Apr 29, 2024
634bea6
Add golden files for new tests
ana-pantilie Apr 29, 2024
857f418
Add documentation to AssocList
ana-pantilie Apr 29, 2024
8e620e5
Add docs to tests
ana-pantilie Apr 29, 2024
721d82d
Add data encoding test
ana-pantilie Apr 29, 2024
0f1d945
Address some review comments
ana-pantilie Apr 30, 2024
7dbd045
Rename AssocList to AssocMap
ana-pantilie May 7, 2024
0b51d91
Make Map newtype over BuiltinList Pair
ana-pantilie May 7, 2024
75edbf9
Fix union implementation
ana-pantilie May 8, 2024
cd2d843
Use BuiltinList internal functions
ana-pantilie May 8, 2024
4071a8a
Create internal top-level delete
ana-pantilie May 8, 2024
7d96dc4
Add union test
ana-pantilie May 8, 2024
2862dec
Add docs to integration tests
ana-pantilie May 8, 2024
1f7f634
Try naive type families
effectfully May 9, 2024
94ff0ff
Split 'Has*' into 'From*' and 'To*' again
effectfully May 10, 2024
1f0d147
Remove type families from '*Opaque'
effectfully May 10, 2024
8a02b66
Add 'ToBuiltin'
effectfully May 11, 2024
669e25f
Add 'TestInstances'
effectfully May 11, 2024
bb18d98
Make it work for GHC-8.10
effectfully May 11, 2024
0ec5c85
Polishing
effectfully May 12, 2024
173ee90
Improve docs
effectfully May 13, 2024
6e329d6
Polishing
effectfully May 13, 2024
ee05a90
Merge remote-tracking branch 'origin/master' into ana/data-assoclist
ana-pantilie May 13, 2024
29f0b1a
Address comments
effectfully May 14, 2024
f98841e
Fix compilation errors in AssocMap
ana-pantilie May 14, 2024
38270be
Add utils from bench package to plutus-tx-plugin tests
ana-pantilie May 14, 2024
a033dc0
Run first PlutusTx property test
ana-pantilie May 14, 2024
bc462b7
WIP: add makeLift to new Map type
ana-pantilie May 14, 2024
105d51c
Merge remote-tracking branch 'origin/effectfully/builtins/split-FromB…
ana-pantilie May 14, 2024
b2e34e4
Add first fully working plutus tx property test
ana-pantilie May 14, 2024
2ae4bf1
Merge remote-tracking branch 'origin/master' into ana/data-assoclist
ana-pantilie May 16, 2024
6f77819
Fix issue with insert propety test
ana-pantilie May 16, 2024
c57c9b7
Run all tests with PlutusTx
ana-pantilie May 16, 2024
678992d
Add changelog
ana-pantilie May 16, 2024
8cda32b
Fix test module warning
ana-pantilie May 21, 2024
393162c
Fix isData instance for These
ana-pantilie May 21, 2024
361cba3
Fix delete implementation
ana-pantilie May 21, 2024
72334a5
Fix redundancy
ana-pantilie May 21, 2024
c5c85ed
Address other review comments
ana-pantilie May 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add documentation to AssocList
Signed-off-by: Ana Pantilie <ana.pantilie95@gmail.com>
  • Loading branch information
ana-pantilie committed Apr 29, 2024
commit 857f418818f2e568f74e8744eefc3c537075cee1
51 changes: 40 additions & 11 deletions plutus-tx/src/PlutusTx/Data/AssocList.hs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ Therefore this implementation is likely a better choice than @PlutusTx.AssocMap.
if it is part of a data type defined using @asData@, and the key and value types
have efficient `P.toBuiltinData` and `P.unsafeFromBuiltinData` operations (e.g., they
are primitive types or types defined using @asData@).

An `AssocList` is considered well-defined if it has no duplicate keys. Most operations
preserve the definedness of the resulting `AssocList` unless otherwise noted.
-}
newtype AssocList k a = AssocList P.BuiltinData
deriving stock (Haskell.Eq, Haskell.Show)
Expand All @@ -63,12 +66,16 @@ instance P.UnsafeFromData (AssocList k a) where
unsafeFromBuiltinData = AssocList
ana-pantilie marked this conversation as resolved.
Show resolved Hide resolved

{-# INLINEABLE lookup #-}
-- | Look up the value corresponding to the key.
-- If the `AssocList` is not well-defined, the result is the value associated with
-- the left-most occurrence of the key in the list.
lookup :: forall k a. (P.ToData k, P.UnsafeFromData a) => k -> AssocList k a -> Maybe a
lookup (P.toBuiltinData -> k) m = case lookup' k (toBuiltinList m) of
ana-pantilie marked this conversation as resolved.
Show resolved Hide resolved
Just a -> Just (P.unsafeFromBuiltinData a)
ana-pantilie marked this conversation as resolved.
Show resolved Hide resolved
Nothing -> Nothing

{-# INLINEABLE lookup' #-}
-- | Similar to 'lookup', but operates on the underlying `BuiltinList` representation.
lookup' ::
BuiltinData ->
BI.BuiltinList (BI.BuiltinPair BuiltinData BuiltinData) ->
Expand All @@ -87,10 +94,12 @@ lookup' k = go
)

{-# INLINEABLE member #-}
-- | Check if the key is in the `AssocList`.
member :: forall k a. (P.ToData k) => k -> AssocList k a -> Bool
member (P.toBuiltinData -> k) m = member' k (toBuiltinList m)

{-# INLINEABLE member' #-}
-- | Similar to 'member', but operates on the underlying `BuiltinList` representation.
member' :: BuiltinData -> BI.BuiltinList (BI.BuiltinPair BuiltinData BuiltinData) -> Bool
member' k = go
where
Expand All @@ -107,11 +116,14 @@ member' k = go
)

{-# INLINEABLE insert #-}
-- | Insert a key-value pair into the `AssocList`. If the key is already present,
-- the value is updated.
insert :: forall k a. (P.ToData k, P.ToData a) => k -> a -> AssocList k a -> AssocList k a
insert (P.toBuiltinData -> k) (P.toBuiltinData -> a) m =
unsafeFromBuiltinList $ insert' k a (toBuiltinList m)

{-# INLINEABLE insert' #-}
-- | Similar to 'insert', but operates on the underlying `BuiltinList` representation.
insert'
:: BuiltinData
-> BuiltinData
Expand All @@ -134,6 +146,9 @@ insert' (P.toBuiltinData -> k) (P.toBuiltinData -> a) = go
)

{-# INLINEABLE delete #-}
-- | Delete a key value pair from the `AssocList`.
-- If the `AssocList` is not well-defined, it deletes the pair associated with the
-- left-most occurrence of the key in the list.
delete :: forall k a. (P.ToData k) => k -> AssocList k a -> AssocList k a
delete (P.toBuiltinData -> k) m = unsafeFromBuiltinList (go (toBuiltinList m))
where
Expand All @@ -152,20 +167,26 @@ delete (P.toBuiltinData -> k) m = unsafeFromBuiltinList (go (toBuiltinList m))
)

{-# INLINEABLE singleton #-}
-- | Create an `AssocList` with a single key-value pair.
singleton :: forall k a. (P.ToData k, P.ToData a) => k -> a -> AssocList k a
singleton (P.toBuiltinData -> k) (P.toBuiltinData -> a) = unsafeFromBuiltinList xs
where
xs = BI.mkCons (BI.mkPairData k a) nil

{-# INLINEABLE empty #-}
-- | An empty `AssocList`.
empty :: forall k a. AssocList k a
empty = unsafeFromBuiltinList nil

{-# INLINEABLE null #-}
-- | Check if the `AssocList` is empty.
null :: forall k a. AssocList k a -> Bool
null = P.null . toBuiltinList

{-# INLINEABLE safeFromList #-}
-- | Create an `AssocList` from a list of key-value pairs.
-- In case of duplicates, this function will keep only one entry (the one that precedes).
-- In other words, this function de-duplicates the input list.
safeFromList :: forall k a . (Eq k, P.ToData k, P.ToData a) => [(k, a)] -> AssocList k a
safeFromList =
unsafeFromBuiltinList
Expand All @@ -181,13 +202,18 @@ safeFromList =
else (k', v') : go k v rest

{-# INLINEABLE unsafeFromList #-}
-- | Unsafely create an 'AssocList' from a list of pairs.
-- This should _only_ be applied to lists which have been checked to not
-- contain duplicate keys, otherwise the resulting 'AssocList' will contain
-- conflicting entries (two entries sharing the same key), and therefore be ill-defined.
unsafeFromList :: (P.ToData k, P.ToData a) => [(k, a)] -> AssocList k a
unsafeFromList =
unsafeFromBuiltinList
. toBuiltin
. PlutusTx.Prelude.map (\(k, a) -> (P.toBuiltinData k, P.toBuiltinData a))

{-# INLINEABLE uncons #-}
-- | Decompose an 'AssocList' into its first key-value pair and the rest of the list.
uncons ::
forall k a.
(P.UnsafeFromData k, P.UnsafeFromData a) =>
Expand All @@ -200,6 +226,9 @@ uncons m = case P.uncons (toBuiltinList m) of
in Just ((P.unsafeFromBuiltinData k, P.unsafeFromBuiltinData a), unsafeFromBuiltinList rest)

{-# INLINEABLE unsafeUncons #-}
-- | Decompose an 'AssocList' into its first key-value pair and the rest of the list.
-- This function is unsafe because it assumes that the elements of the list can be safely
-- decoded from their 'BuiltinData' representation.
unsafeUncons ::
forall k a.
(P.UnsafeFromData k, P.UnsafeFromData a) =>
Expand All @@ -212,6 +241,7 @@ unsafeUncons m =
(k, a) = P.pairToPair hd

{-# INLINEABLE noDuplicateKeys #-}
-- | Check if the `AssocList` is well-defined. Warning: this operation is O(n^2).
noDuplicateKeys :: forall k a. AssocList k a -> Bool
noDuplicateKeys m = go (toBuiltinList m)
where
Expand All @@ -226,6 +256,7 @@ noDuplicateKeys m = go (toBuiltinList m)
)

{-# INLINEABLE all #-}
--- | Check if all values in the `AssocList` satisfy the predicate.
all :: forall k a. (P.UnsafeFromData a) => (a -> Bool) -> AssocList k a -> Bool
all p m = go (toBuiltinList m)
where
Expand All @@ -240,6 +271,7 @@ all p m = go (toBuiltinList m)
)

{-# INLINEABLE any #-}
-- | Check if any value in the `AssocList` satisfies the predicate.
any :: forall k a. (P.UnsafeFromData a) => (a -> Bool) -> AssocList k a -> Bool
any p m = go (toBuiltinList m)
where
Expand All @@ -255,13 +287,7 @@ any p m = go (toBuiltinList m)

{-# INLINEABLE union #-}

-- TODO: This is broken!
-- The value should be a correct encoding of a `These` value, but it is not.
-- Example:
-- > union (safeFromList []) (safeFromList [(0, 0)]) :: AssocList Integer (These Integer Integer)
-- > AssocList Map [(I 0,I 0)]
-- The second element of the pair should be encoded as the appropriate `Constr`!
-- | Combine two 'AssocList's.
-- | Combine two 'AssocList's into one. It saves both values if the key is present in both lists.
union ::
forall k a b.
(P.UnsafeFromData a, P.UnsafeFromData b, P.ToData a, P.ToData b) =>
Expand Down Expand Up @@ -376,10 +402,8 @@ unionWith f (toBuiltinList -> ls) (toBuiltinList -> rs) =
(\hd -> go (BI.mkCons hd acc))

{-# INLINEABLE toList #-}

{- | `toList` is expensive since it traverses the whole map.
`toBuiltinList` is much faster.
-}
-- | Convert the `AssocList` to a list of key-value pairs. This operation is O(n).
-- See 'toBuiltinList' for a more efficient alternative.
toList :: (P.UnsafeFromData k, P.UnsafeFromData a) => AssocList k a -> [(k, a)]
toList d = go (toBuiltinList d)
where
Expand All @@ -393,16 +417,21 @@ toList d = go (toBuiltinList d)
)

{-# INLINEABLE toBuiltinList #-}
-- | Convert the `AssocList` to a `P.BuiltinList` of key-value pairs. This operation is O(1).
toBuiltinList :: AssocList k a -> BI.BuiltinList (BI.BuiltinPair BuiltinData BuiltinData)
ana-pantilie marked this conversation as resolved.
Show resolved Hide resolved
toBuiltinList (AssocList d) = BI.unsafeDataAsMap d

{-# INLINEABLE unsafeFromBuiltinList #-}
-- | Unsafely create an 'AssocList' from a `P.BuiltinList` of key-value pairs.
-- This function is unsafe because it assumes that the elements of the list can be safely
-- decoded from their 'BuiltinData' representation.
unsafeFromBuiltinList ::
forall k a.
BI.BuiltinList (BI.BuiltinPair BuiltinData BuiltinData) ->
AssocList k a
unsafeFromBuiltinList = AssocList . BI.mkMap

{-# INLINEABLE nil #-}
-- | An empty `P.BuiltinList` of key-value pairs.
nil :: BI.BuiltinList (BI.BuiltinPair BuiltinData BuiltinData)
nil = BI.mkNilPairData BI.unitval