-
Notifications
You must be signed in to change notification settings - Fork 483
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
Conversation
@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. |
There was a problem hiding this comment.
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``. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 | ||
========================================= | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. | |
3a69304
to
15b7e83
Compare
* Add documentation for AsData * fix * Remove rogue dump option * WIP * comments
This adds a basic howto guide showing how to use it and discussing what it does.