-
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 let binding to UPLC? #4797
Comments
The whole point of PLC is to be very low level and not include such things. It sounds like you're gradually re-inventing PIR, which exists exactly to add types, (recursive) let-bindings, and datatypes :) |
The point is to increase performance of scripts. |
Why do you think it would be faster? In both cases most of the work is evaluating the RHS and putting it into the environment. |
Smaller AST. |
Here's my more detailed analysis. First, what happens when we evaluate the two versions?
So indeed So what does this save us? One compute-return step per let-binding, which corresponds to:
Of these we have evidence that 4 makes some difference from #5042, but we can also avoid that by doing something like the optimization in that PR. The others are very cheap except for 3, which might matter. The other factor is that it depends how hot (i.e. frequently evaluated) let-bindings are. My belief is that they are currently not that hot, see below for reasoning. We can use the sums-of-products work as a convenient testbed for this. Sums-of-products introduces a bunch of immediately-applied lambdas whenever we evaluate a pattern match. This could instead be let-terms. Moreover, I implemented two versions of sums-of-products, one where case expressions bind variables, and one where they just apply lambdas. The former corresponds to having let-terms. The version which bound variables had about a 10% speedup over the one that didn't, but we could reclaim 5% of it with #5042, i.e. cause 4 above seems to account for half of the wastage, and is independently fixable. But that PR makes only a 2% difference on master, which also tells us that sums-of-products introduces far more let-term opportunities than exist on master. Regarding multi-lets versus multi-lambdas: I believe this would dilute the advantage of multi-lets even further. The advantage of let is just that it lets us avoid evaluating the lambda; but with multi lambdas we still only do one lambda evaluation, but this time for multiple arguments. So the overhead is proportionally less. In all other respects I'd expect them to have the same behaviour. Summing up:
|
I'm going to claim that:
Hence I'm adding the |
From my perspective, this is a very big deal. Using a let while folding on inputs / outputs is very common. 10% is very significant (and tests that I have done approximate the efficiency benefits at ~13-16%). The complexity of this language construct seems relatively low in comparison. |
@colll78 I do also like the idea of adding
Please consider sharing those, that can potentially help us quite a bit. |
Yes, I would like to see the evidence. As I argued above, I'm not as convinced of the benefits. |
I've spent some time thinking about it and I believe we should definitely try adding |
If we don't add a Currently we pretty-print applied lambdas as:
which is ok in this small example, but in a bigger one it’s a lot of unnecessary indentation, the “RHS” of the variable can be very far from its LHS and the whole thing is generally quite unreadable, especially when some of the application are iterated (check it out yourself by reading the program in this comment). This makes it harder than necessary to read the output of the readable pretty-printer, which has negative consequences:
This ticket is for changing the pretty-printer to render the above term as
or something similar. We’ve done a lot to make Plutus Core readable and it did help. Rendering applied lambdas as lets will make reading Plutus Core scripts even more manageable, making us overall a bit more productive. |
A great reference you can use for really nice UPLC pretty printing is: It does exactly what you are describing (rendering applied lambdas as lets) ie:
let frSndPair = !!sndPair
unDataSum = (\xF -> frSndPair (unConstrData xF))
frTailList = !tailList
frHeadList = !headList
frIfThenElse = !ifThenElse
in (\oP4ECBT qsrxlF0Y7 ->
let cjlB6yrGk = unDataSum qsrxlF0Y7
cRFO = unConstrData (frHeadList (frTailList cjlB6yrGk))
cs9iR = !!fstPair cRFO
w4 = frSndPair cRFO
in It also does a ton of other stuff that makes the UPLC much more readable including: Lambdas are prettified similar to haskell lambdas, i.e |
@colll78 thank you for the reference. I'm particularly glad that others do this, because we've had some debates internally whether it's weird or not to render applied lambdas as lets.
We have all of that in the form of an alternative Agda-like syntax called "readable". Here's an example in the typed lang:
|
We're moving tickets from Jira, so here's some context that we had there (written by me, so biased in favor of adding
|
I'm sure it is already mentioned but reporting that case and contr now are almost native lets |
Describe the feature you'd like
Currently, in Plutarch (and I assume same for PlutusTx), we're emulating let bindings
let x = y; z
as(\x -> z) y
, but this has the cost of introducing then eliminating a lambda.If we had a
Let
constructor or similar, it's possible that it would be faster to interpret common scripts, if they were made to use native let-bindings.Describe alternatives you've considered
No response
The text was updated successfully, but these errors were encountered: