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

Support for Either type in tact #379

Open
0kenx opened this issue Jun 5, 2024 · 13 comments
Open

Support for Either type in tact #379

0kenx opened this issue Jun 5, 2024 · 13 comments
Assignees
Labels
discussion Ideas that are not fully formed and require discussion language feature TL-B https://docs.ton.org/develop/data-formats/tl-b-types
Milestone

Comments

@0kenx
Copy link
Contributor

0kenx commented Jun 5, 2024

Tact should add support for Either TL-B type.

I propose we use the ? operator: TL-B Either X Y -> Tact X ? Y. If it's too confusing we could also use a more explicit syntax Either<X, Y>.

@anton-trunov anton-trunov added this to the v1.4.0 milestone Jun 5, 2024
@anton-trunov
Copy link
Member

Yes, this is needed to implement #322. I'd propose to use the pipe symbol | instead of ? because it's essentially a type analogue of logical disjunction and there won't be grammar conflicts with the optional type operator:

Type = typeId "?" --optional
.

We should also outline the concrete syntax for the constructors and destructors for the Either type.

To construct an expression of an Either type, we need two constructors, we can reuse the Left and Right names that TL-B borrows from Haskell, because it's more general than what, for instance, Rust does with Ok and Error.

To destruct an expression of an Either type, we need pattern-matching:

let foo: X | Y = ...;
match foo {
  Left(l) => {
    return 42;
  }
  Right(r) => {
    return 24;
  }
}

or, in Ohm.js-like notation:

StatementMatch ::= "match" Expression "{" ListOf<MatchClause, ","> ","? "}"

MatchClause ::= Pattern "=>" "{" Statement* "}"

// patterns can be nested
Pattern ::= Constructor ("(" NonemptyListOf<Pattern,","> ","? ")")?
        | TactIdentifier
        | "_"

...

This could be good enough for an initial implementation, later we could support things like | for grouping patterns, etc. The initial implementation does not have to support the general algebraic data types, just the Either type.

@0kenx
Copy link
Contributor Author

0kenx commented Jun 6, 2024

The only problem I have with using | is that, in many languages with sum types, | is either straight away commutative, or implementation details are hidden well enough so developers can treat it as commutative.

TL-B on the other hand, makes a clear distinction with the selector bit. And because we want Tact code to interoperate with TL-B definitions, there's no way to abstract that implementation detail away.

@anton-trunov
Copy link
Member

Well, because it is commutative. In the sense that you can always build an isomorphism between Either X Y and Either Y X.

@0kenx
Copy link
Contributor Author

0kenx commented Jun 6, 2024

Yup, but serializing type X in Either X Y would result in 0b0xxxxxxxx, and serializing X in Either Y X would result in 0b1xxxxxxxx. If people take the type from FunC and change the order without realizing what is under the hood it would lead to errors.

@anton-trunov
Copy link
Member

Yeah, that's a good point, but still more verbose syntax won't prevent this. I'm happy to change my opinion if you have a convincing concrete example, though

@novusnota
Copy link
Member

novusnota commented Jun 7, 2024

We may come up with an or keyword (as in Either ... or ...). It won't describe the layout, but it also won't be confused with general ADTs (not nearly as much as | would).

let foo: X or Y = ...;

As a bonus it pairs nicely with as serialization modifier, introducing a stronger visual bond between left and right parts as opposed to | or comma in Either<..., ...>

More examples:

// Maybe of Eithers'
let foo: (X or Y)? = ...;

// Alternative Maybe of Eithers'
let foo2: X or? Y = ...;

// Either of Maybes'
let bar: X? or Y? = ...;

// Throwing as into the mix
let baz: (X as uint64 or Y as coins)? = ...;
let baz2: X as uint64 or? Y as coins = ...; // alternative maybe of eithers'

@novusnota novusnota added the discussion Ideas that are not fully formed and require discussion label Jun 7, 2024
@Gusarich
Copy link
Member

Gusarich commented Jun 8, 2024

// Alternative Maybe of Eithers'
let foo2: X or? Y = ...;

@novusnota what’s that in TL-B notation?

@anton-trunov anton-trunov modified the milestones: v1.4.0, v1.5.0 Jun 12, 2024
@novusnota
Copy link
Member

@Gusarich Something like this, I guess:

// TL-B: Either X Y
let ex1: X or Y = ...; // just the Either

// TL-B: Maybe (Either X Y))
let ex2: X or? Y = ...; // Maybe of Either's, like here:
                        // https://github.com/ton-blockchain/ton/blob/140320b0dbe0bdd9fd954b6633a3677acc75a8e6/crypto/block/block.tlb#L155-L157

// TL-B: Either (Maybe X) (Maybe Y)
let ex3: X? or Y? = ...; // Either of Maybe's

// TL-B: Maybe (Either (Maybe X) (Maybe Y))
let ex4: X? or? Y? = ...; // Maybe of Either of Maybe's (inception!)

@anton-trunov
Copy link
Member

@novusnota We should just allow ? to be used for any type, not only type idents, hence X or? Y becomes (X or Y)?

@Gusarich
Copy link
Member

I tried to implement this, but I came across the fact that in the type ABITypeRef from @ton/core there is no way to define either type.

@anton-trunov anton-trunov added the TL-B https://docs.ton.org/develop/data-formats/tl-b-types label Aug 30, 2024
@anton-trunov anton-trunov self-assigned this Sep 5, 2024
@anton-trunov anton-trunov modified the milestones: v1.5.0, v1.6.0 Sep 15, 2024
@novusnota
Copy link
Member

novusnota commented Nov 25, 2024

Just remembered that Kotlin (the language we name in the first commandment of Tact) uses as for type casts. See "unsafe" cast operator and "safe" nullable cast operator in their docs.

as and as? type casts performed

I'm not saying that we should have type casts (we probably don't, except for our !! operator), but the general idea of as? looks reminiscent of my ideas of or? syntax prior.

But this is also fine:

We should just allow ? to be used for any type, not only type idents, hence X or? Y becomes (X or Y)?

@verytactical
Copy link
Contributor

Yes, obviously unsafe type casts have no place in security/finance applications.

Maybe and Either types are boolean-blind and exist in functional programming languages mostly for implementation of generic libraries that do not care about semantics of stored values. Contracts are not generic, do care about semantics, and shouldn't use either Either or Maybe.

I understand how adding Either might sound simpler than full-fledged algebraic data types, but winging it here will degrade code quality of the whole Tact ecosystem in the long run, and in my opinion just isn't worth it.

@Gusarich
Copy link
Member

@verytactical but the thing is that token standards have Either in their message schemas so we have to give a good way to define such structs.

Maybe we should add some flag for that, so that it's possible to strictly check which of the Either options was used during parsing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Ideas that are not fully formed and require discussion language feature TL-B https://docs.ton.org/develop/data-formats/tl-b-types
Projects
None yet
Development

No branches or pull requests

5 participants