Extending Helios to support destructuring
vanusquarm opened this issue ยท 20 comments
In your example you've defined an enum with three variants (newlines don't matter)
Currently you can wrap Buyer
:
enum Redeemer {
Cancel
Buy {
buyer: Buyer
}
}
It's a bit more verbose, but defining methods on Buyer
can compensate for that
(please don't get discouraged by me being a bit difficult, I'd like to continue the discussion)
What do you think about the following:
struct Buyer {
id: PubKeyHash
amount: Int
}
enum Redeemer {
Cancel
Buy {
buyer: Buyer
}
}
func main(redeemer: Redeemer) -> Bool {
redeemer.switch{
Cancel => true,
{b: Buyer}: Buy => {...}
}
}
// and even nested destructuring
func main(redeemer: Redeemer) -> Bool {
redeemer.switch{
Cancel => true,
{{pkh: PubKeyHash, _}}: Buy => {...}
}
}
// optional typing of intermediate structures
func main(redeemer: Redeemer) -> Bool {
redeemer.switch{
Cancel => true,
{{pkh: PubKeyHash, _}: Buyer}: Buy => {...}
}
}
The same syntax could be used for function arguments and assignments:
func do_something_w_buyer_pkh({pkh: PubKeyHash, _}: Buyer) -> Bool {
...
}
// or
func do_something_w_buyer_pkh(buyer: Buyer) -> Bool {
{pkh: PubKeyHash, amount: Int}: Buyer = buyer
}
Note: in this proposal the destructuring is positional, not field-name based
- ๐ for the recursive destructuring
- ๐ as in scala, it should be only positional
- ๐ I like the deconstructing in the method signature, one less declaration in the code.
- I am used to the scala syntax and I would prefer the type to be declared before the attributes Buyer(pkh: PubKeyHash, amount: Int). Not sure if there are language/framework constraints. But I can deffo get used to the type being specified after (that in fairness would follow the convention
variable: Type
. - One request I might have, in scala there is the conditional match:
case Buyer(_: PubKeyHash, amount: Int) if amount > 0 =>
. It would be awesome if you could implement it as part of this effort.
Type before might work. Would it be better with curly braces though (so it matches literal struct expressions)?
Let's see how it looks when mixing that with the two case syntaxes that already exist:
redeemer.switch{
c: Cancel => {...},
Buy{Buyer{pkh: PubKeyHash, _}} => {...},
(i: Int, _) => {...}
}
// the intermediate struct type is optional
redeemer.switch{
c: Cancel => {...},
Buy{{pkh: PubKeyHash, _}} => {...},
(i: Int, _) => {...}
}
And for function arguments and assignments:
func do_something_w_buyer_pkh(Buyer{pkh: PubKeyHash, _}, other_arg: OtherType) -> Bool {
...
}
// or
func do_something_w_buyer_pkh(buyer: Buyer) -> Bool {
Buyer{pkh: PubKeyHash, amount: Int} = buyer
}
I feel like this works for assignments, but not so much for function arguments
Thanks for the examples, it actually helps a lot seeing them. On a second thought. Maybe type after is better. It's more homogeneous.
Out of curiosity, if intermediate type in this case is optional Buy{{pkh: PubKeyHash, _}} => {...},
, why is PubKeyHash
required
PubKeyHash
is required so the reader knows the type of pkh
just be looking at this code. If we allow type-inference here then the reader would have to dig deeper
Note that this issue is only about destructuring, not about special pattern matching syntax
Note that this issue is only about destructuring, not about special pattern matching syntax
ahah ok ok got excited and carried a bit away ...
What do you think about the following:
struct Buyer { id: PubKeyHash amount: Int } enum Redeemer { Cancel Buy { buyer: Buyer } } func main(redeemer: Redeemer) -> Bool { redeemer.switch{ Cancel => true, {b: Buyer}: Buy => {...} } } // and even nested destructuring func main(redeemer: Redeemer) -> Bool { redeemer.switch{ Cancel => true, {{pkh: PubKeyHash, _}}: Buy => {...} } } // optional typing of intermediate structures func main(redeemer: Redeemer) -> Bool { redeemer.switch{ Cancel => true, {{pkh: PubKeyHash, _}: Buyer}: Buy => {...} } }
I've just noticed on discord you're working on this, can you confirm this is the design you're following? I would be supportive of this syntax.
I think this design is the most natural extension of the current syntax (because people would rewrite buy: Buy
as {buyer: Buyer}: Buy
or {{pkh: PubKeyHash, _}}: Buy
.
I would like to get more people involved in the discussion though
What do you think about the following:
struct Buyer { id: PubKeyHash amount: Int } enum Redeemer { Cancel Buy { buyer: Buyer } } func main(redeemer: Redeemer) -> Bool { redeemer.switch{ Cancel => true, {b: Buyer}: Buy => {...} } } // and even nested destructuring func main(redeemer: Redeemer) -> Bool { redeemer.switch{ Cancel => true, {{pkh: PubKeyHash, _}}: Buy => {...} } } // optional typing of intermediate structures func main(redeemer: Redeemer) -> Bool { redeemer.switch{ Cancel => true, {{pkh: PubKeyHash, _}: Buyer}: Buy => {...} } }
Great work Christian. Love the use-cases, however, will like to discourage the introduction of deep nesting as seen in the last two examples as this introduced a new level of complexities such as the difficulties in tracking missing objects and subsequently the possibilities of getting many undefined errors. Eslint recently removed support for deep nested destructuring with reason of promoting more readable and comprehensive code.
@vanusquarm are there any strongly typed languages that support deeply nested destructuring? (the typing would prevent undefined errors)
You might be right about the readability argument though. However, as enum variants can't yet be assigned type directly (it has to be wrapped), this could be a convenient way to destructure those variants in a single expression
Has been implemented in v0.13.17 of the library.
The syntax differs a bit from what was proposed above, but I think the final implementation is very consistent and conventional
https://www.hyperion-bt.org/helios-book/lang/destructuring.html
A version of the example we've been using above using the latest version:
spending my_validator
struct Buyer {
id: PubKeyHash
amount: Int
}
enum Redeemer {
Cancel
Buy {
buyer: Buyer
}
}
func main(_, redeemer: Redeemer, _) -> Bool {
redeemer.switch{
Cancel => true,
Buy{b} => {doSomething(b)}
}
}
// and even nested destructuring
func main(_, redeemer: Redeemer, _) -> Bool {
redeemer.switch{
Cancel => true,
Buy{Buyer{pkh, _}} => {doSomething2(pkh)}
}
}
This is PERFECT