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 documentation for AsData #5729

Merged
merged 5 commits into from
Mar 21, 2024
Merged

Add documentation for AsData #5729

merged 5 commits into from
Mar 21, 2024

Conversation

michaelpj
Copy link
Contributor

This adds a basic howto guide showing how to use it and discussing what it does.

@michaelpj michaelpj requested a review from zliu41 January 18, 2024 13:59
@kwxm kwxm added the No Changelog Required Add this to skip the Changelog Check label Jan 18, 2024
@joseph-fajen
Copy link
Contributor

@michaelpj I'm working on a suggestion or two about how this is organized but I haven't yet worked out the specifics. More soon.

This means that when a script starts to work with its datum or redeemer, it often wants to parse it into a more structured format using Plutus Tx's support for datatypes.
Usually this is done with the ``fromBuiltinData`` function.

The problem is that ``fromBuiltinData`` has to traverse the entire ``Data`` object in order to perform the transformation, which is costly.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be useful to point out that this is the case even if your script only accesses a part of the datum/redeemer, or indeed even if it doesn't access them at all. This may not be obvious especially if people expect laziness from Haskell.

Caveats
-------

The most important caveat to using ``asData`` is that ``Data`` objects encoding datatypes must also encode the *fields* ``Data``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a must, it just tends to work better that way. For small types like Integer or even Maybe Integer there's usually no need to bother.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what you mean. It absolutely is the case that the fields of data objects must be data. Constr takes a list of data objects as fields, there is no way to put anything else in there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took this sentence to mean: if you use AsData on data Foo = Foo { bar :: Bar, ... }, then you must also use AsData on Bar. But you don't have to: Bar can just be a regular Haskell datatype.

But now I don't think this is what you mean.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I said that the data object must encode its fields as Data. So if you use AsData on Foo, then bar must be encoded using Data. It's just that that's going to be expensive if Bar is a normal datatype and cheap if it's using AsData.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, I clearly need to make this clearer!


For example, in the ``Bid`` case above the ``Lovelace`` field is represented as a Plutus Core builtin integer.
However, in order for it to be encoded into the ``Bid`` structure, we need to encode it into ``Data``.
That means that when you construct a ``Bid`` object you do an ``Integer`` to ``Data`` conversion, and when you pattern match on a ``Bid`` object you do a ``Data`` to ``Integer`` conversion.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think Integer is a very good example, since there's almost certainly no need to bother with a Data-encoded Integer type, vs. just the plain Integer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused. The point is that you're data-encoding Bid, and that forces you to data-encode its Integer fields (which is cheap, but not free)

But any values of this type actually are represented with ``Data``.
That means that when we newtype-derive the instances for converting to and from ``Data`` we get the instances for ``BuiltinData`` - which are free!

Caveats
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's another important caveat: it is advisable to access fields with pattern matching, since it is probably faster than field accessors. Record patterns can be used if there are too many fields.


How to use ``AsData`` to optimize scripts
=========================================

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The latest Plutus libraries contain a new ``PlutusTx.asData`` module that contains Template Haskell (TH) code for creating algebraic data types (ADTs) as Data objects in Plutus Core, as opposed to sums-of-products terms. In general, ``asData`` pushes the burden of a computation nearer to where a value is used, in a crude sense making the evaluation less strict and more lazy. This is intended for expert Plutus developers.
Purpose
-------
When writing and optimizing a Plutus script, one of the challenges is finding the right balance for your specific use case between which method to use for the data handling and how expensive that method will be. To make an informed decision, you may need to benchmark and profile your smart contract code to measure its actual resource consumption. The primary purpose of ``asData`` is to give you two options for how you want to handle your data.
Choice of two pathways
----------------------
Through ``PlutusTx.asData``, you have a choice of two pathways. It is up to you to determine which pathway to use depending on your particular use case. There are trade offs. The pathway you choose changes where any errors might happen.
Method one: proactively do all of the parsing
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The first approach is to parse the object ahead of time to determine whether or not it is an integer. If it is not an integer, there are issues to resolve. If it is an integer, it will be packaged up.
Method two: only do the parsing if and when necessary
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In the second approach, the validator doesn't necessarily do anything. It only does the work when it needs to. In order to work with the data objects, the validator has to call the builtin functions, which is a little bit more expensive. It might be that this saves you a lot of work, because you may never need to parse the entire object. Instead, the validator will just carry the item around as if it were a data object. Some time later, when the validator needs to do an operation with this integer, then the validator will parse it. If it determines that it is not an integer, there will be errors.
Using this method, every time the validator parses it, it is going to look at the data object to find out if it is an integer. If it is an integer, it will get the integer out and do its processing. The analysis work may be repeated depending on how your script is written. In some cases, you might do less work, in some cases you might do more work, depending on your specific use case.
Using ``asData``
------------------
``asData`` works best when you use it for a type and all the types that go into it. The ``asData`` function takes the definition of a data type and replaces it with an equivalent definition whose representation uses ``Data`` directly.
``Data`` objects versus Plutus Tx's datatypes
---------------------------------------------
There are tradeoffs relating to the slower processing speed of ``Data`` objects versus Plutus Tx's datatypes.
Values stored in datums or redeemers
------------------------------------

This means that when a script starts to work with its datum or redeemer, it often wants to parse it into a more structured format using Plutus Tx's support for datatypes.
Usually this is done with the ``fromBuiltinData`` function.

The problem is that ``fromBuiltinData`` has to traverse the entire ``Data`` object in order to perform the transformation, which is costly.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The problem is that ``fromBuiltinData`` has to traverse the entire ``Data`` object in order to perform the transformation, which is costly.
However, ``fromBuiltinData`` has to traverse the entire ``Data`` object in order to perform the transformation, which is costly.

1. If the resulting object is going to be processed in its entirety, or have parts of it repeatedly processed, then it can be better to convert it, since Plutus Tx's datatypes are faster to work with than ``Data``.
2. If it is important to check that the entire structure is well-formed, then it is better to convert it, since the conversion will check for well-formedness.
3. If correctness is important, then it can be better to convert it, since the compiler can help you more if you are using Plutus Tx's datatypes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
When conversion to Plutus Tx's datatypes is appropriate
-------------------------------------------------------
It is appropriate to convert an object to Plutus Tx's datatypes under the following circumstances:
1. When the resulting object is going to be processed in its entirety, or have parts of it repeatedly processed, since Plutus Tx's datatypes are faster to work with than ``Data``.
2. If it is important to check that the entire structure is well-formed, since the conversion will check for well-formedness.
3. If correctness is important, since the compiler can help you more if you are using Plutus Tx's datatypes.

@michaelpj michaelpj enabled auto-merge (squash) March 21, 2024 11:56
@michaelpj michaelpj merged commit 902cfa3 into master Mar 21, 2024
4 of 5 checks passed
@michaelpj michaelpj deleted the mpj/asdata-doc branch March 21, 2024 12:22
v0d1ch pushed a commit to v0d1ch/plutus that referenced this pull request Dec 6, 2024
* Add documentation for AsData

* fix

* Remove rogue dump option

* WIP

* comments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
No Changelog Required Add this to skip the Changelog Check
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants