Stream: contributing

Topic: Ambigous syntax for tuples and functions


view this post on Zulip Wizard ish (Dec 13 2024 at 20:31):

While fixing #2457, there was a much larger problem that I found in crates/compiler/test_syntax/tests/snapshots/pass/annotate_tuple_func.expr.roc. Namely, this syntax is ambiguous
The problem in short is that if we have a definition of the form

func : (x,y->z)

this can be interpreted in two ways

  1. func is the type of a singleton couple who's only element is a function that takes types x and y and returns z
  2. func is the type of a size two tuple, the first of which has the type x, the second the type y->z.
    Which of these should be preferred?

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 20:35):

I think functions in tuples require parens

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 20:36):

So that is not ambigious. It can only be a function

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 20:36):

That is at least my understanding of the syntax today

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 20:36):

For that to be a tuple, it would have to be tup : (x,(y->z))

view this post on Zulip Wizard ish (Dec 13 2024 at 20:37):

Ok, this isn't the behavior in the snapshot, i didn't realize until i really looked at it that it was actually wrong, but this will also fix that as well, so two birds one stone ig

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 20:37):

interesting

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 20:38):

Also, someone else with more parser knowledge may jump in later and correct what I said above, but I think it is correct.

view this post on Zulip Wizard ish (Dec 13 2024 at 20:39):

so just to be clear this change to the snapshot ought to be approved?

@0-16 Defs(
     Defs {
         tags: [
             EitherIndex(2147483648),
         ],
         regions: [
             @0-13,
         ],
         space_before: [
             Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
         ],
         space_after: [
             Slice<roc_parse::ast::CommentOrNewline> { start: 0, length: 0 },
         ],
         spaces: [],
         type_defs: [],
         value_defs: [
             Annotation(
                 @0-1 NumLiteral(
                     "1",
                 ),
                 @2-13 Tuple {
                     elems: [
<                        @3-4 SpaceAfter(
<                            BoundVariable(
<                                "f",
<                            ),
>                        @3-11 Function(
                             [
<                                Newline,
<                            ],
<                        ),
<                        @6-11 Function(
<                            [
>                                @3-4 BoundVariable(
>                                    "f",
>                                ),
                                 @6-8 BoundVariable(
                                     "ww",
                                 ),
                             ],
                             Pure,
                             @10-11 BoundVariable(
                                 "p",
                             ),
                         ),
                     ],
                     ext: Some(
                         @12-13 BoundVariable(
                             "e",
                         ),
                     ),
                 },
             ),
         ],
     },
     @14-16 SpaceBefore(
         Tag(
             "Mh",
         ),
         [
             Newline,
         ],
     ),
 )

(from

1:(f
,ww->p)e
Mh

)

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 20:40):

I don't think it should be in a tuple at all

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 20:40):

func : (x,y->z) should be the same as func : x,y->z

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 20:40):

Cause we don't have 1 element tuples

view this post on Zulip Wizard ish (Dec 13 2024 at 20:40):

wait really, why not?

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 20:41):

#ideas > 1-tuples

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 20:42):

Has at least a little bit of context

view this post on Zulip Wizard ish (Dec 13 2024 at 20:44):

hmmm ok so that to

view this post on Zulip Wizard ish (Dec 13 2024 at 20:44):

wait is that covered in can?

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 20:45):

My understanding there is nothing in explicitly blocks 1 tuples, we just never generate them when parsing.

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 20:45):

but that is just a guess

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 20:46):

@Sam Mohr or @Joshua Warner would know a lot better

view this post on Zulip Sam Mohr (Dec 13 2024 at 20:47):

Yeah, we support 1 element tuples as far as I can tell past parsing. We need them for the custom types approach to aliasing types

view this post on Zulip Sam Mohr (Dec 13 2024 at 20:48):

Because MyStr := Str will be a 1-tuple around Str

view this post on Zulip Sam Mohr (Dec 13 2024 at 20:50):

It's more that Roc stays a small language by only including features that we need, and there doesn't seem to be a good use case for 1-tuples being definable as literals

view this post on Zulip Wizard ish (Dec 13 2024 at 20:53):

okay then, so then that needs to be implemented because it appears to view (T) as a (valid) type

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 21:00):

@Sam Mohr what do you think func : (x,y->z) should parse to?

view this post on Zulip Wizard ish (Dec 13 2024 at 21:06):

oh and something else i realized, e is a type extension, so how should that go over, as one cannot ask for the rest of a functions fields...

view this post on Zulip Wizard ish (Dec 13 2024 at 21:09):

wait can tuples even have extensions?

view this post on Zulip Sam Mohr (Dec 13 2024 at 21:12):

For that to be a tuple, it would have to be tup : (x,(y->z))

view this post on Zulip Sam Mohr (Dec 13 2024 at 21:12):

Your suggestion is my vote

view this post on Zulip Sam Mohr (Dec 13 2024 at 21:12):

That's already how we format tuples in type signatures with functions in them

view this post on Zulip Wizard ish (Dec 13 2024 at 21:12):

and can tuples have extensions?

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:13):

Tuples can have extensions

view this post on Zulip Sam Mohr (Dec 13 2024 at 21:13):

@Brendan Hansknecht so if we wanted what you just asked about to be a function, we should remove the parens

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:13):

That's important at least in the type system. I'm not sure I've ever seen real code that annotated such a type, where the more general annotation enabled by extensions was important

view this post on Zulip Sam Mohr (Dec 13 2024 at 21:13):

But even with parens it would be a 2 argument function to me

view this post on Zulip Wizard ish (Dec 13 2024 at 21:14):

so should (x)y be interpreted as a singleton tuple with extension y or just the types x and y

view this post on Zulip Sam Mohr (Dec 13 2024 at 21:14):

I don't believe we support that syntax

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:14):

Yeah we do :)

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:14):

(x)y is a tuple with an ext, but (x) y is a type application

view this post on Zulip Sam Mohr (Dec 13 2024 at 21:14):

Oh, cool!

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 21:14):

Sam Mohr said:

Brendan Hansknecht so if we wanted what you just asked about to be a function, we should remove the parens

Yeah, I would expect the formatter to remove the parens

view this post on Zulip Wizard ish (Dec 13 2024 at 21:15):

basically the code currently thinks (f,ww->p)e is a tuple with an extension e, but is this incorrect?

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:15):

That sounds wrong to me :thinking:

view this post on Zulip Sam Mohr (Dec 13 2024 at 21:15):

The only reason we really use that at outside of signatures at the moment is for tuple getters

view this post on Zulip Wizard ish (Dec 13 2024 at 21:16):

should i change this then?

if fields.len() > 1 || ext.is_some() {...}

to just

if fields.len() > 1 {...}

(in the type in parens function)
just making sure because this is a little bit confusing

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:18):

Oh wait sorry, I read that wrong

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:18):

That does look correct

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:18):

(for some reason my eyes glossed over the e ext in your example)

view this post on Zulip Wizard ish (Dec 13 2024 at 21:19):

so it should be a tuple ?

view this post on Zulip Wizard ish (Dec 13 2024 at 21:25):

or a function, or should it be rejected?

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:30):

It should be a tuple with a single element, which is itself a function.

view this post on Zulip Wizard ish (Dec 13 2024 at 21:31):

got it

view this post on Zulip Wizard ish (Dec 13 2024 at 21:33):

so then this diff is simply correct
#contributing > Ambigous syntax for tuples and functions @ 💬

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:33):

Yes, that looks right to me

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:33):

What change triggered that?

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 21:34):

Interesting. I would have guess that it was something that shouldn't parse.

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 21:34):

I guess if it parses as a tuple with a function, the formatter would add in the extra parents which is fine

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:35):

Why would there be extra parens?

view this post on Zulip Wizard ish (Dec 13 2024 at 21:35):

this one and then also this one

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 21:36):

I would have thought that only these are valid:

func : (x,y->z)
func : x,y->z

tuple_extension : ((x,y->z))e
tuple_extension : (x,(y->z))e

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 21:36):

Why would there be extra parens?

function signature in tuple type requires parens

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:37):

Ah; I guess the formatter may add extra parens to be conservative, but it doesn't technically have to

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:37):

It'll parse fine if it doesn't, in this special case of a 1-elem tuple

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 21:38):

I just find it exceptionally strange that this is not a tuple type func : (x,y->z), but this is tup: (x,y->z)e

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 21:39):

feels like a huge inconsistency even if it can technically parse

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:39):

TBH tuples are very weird at a syntactic level. As are exts.

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:39):

This grammar is balancing on a knife's edge ;)

view this post on Zulip Wizard ish (Dec 13 2024 at 21:40):

ok yep that's the only test that failed, and is now updated, pr should be ready to go in like 10 minutes

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 21:40):

Also, to be fair, extension are changing and it will fix up this case

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 21:41):

func : (x,y->z)
func : x,y->z

tuple_extension : ((x,y->z), ..e)
tuple_extension : (x,(y->z), ..e)

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 21:41):

Now the parens around the function are clearly needed

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:41):

Oh cool that's so much nicer to parse :heart_eyes:

view this post on Zulip Wizard ish (Dec 13 2024 at 21:42):

also why not use one deliminator for tuples and another for just grouping (in types)

view this post on Zulip Luke Boswell (Dec 13 2024 at 21:42):

See https://github.com/roc-lang/roc/issues/7091
for Simplify Types with .. Spread Syntax

view this post on Zulip Joshua Warner (Dec 13 2024 at 21:44):

also why not use one deliminator for tuples and another for just grouping (in types)

Reasonable question. Not sure if alternative delimiters were considered when discussing the implementation for tuples.

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 21:51):

I don't think it was every discussion. People are too used to tuples being (..., ..., ...)

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 21:52):

We did discuss maybe following python and allowing a one tuple as (...,)

view this post on Zulip Brendan Hansknecht (Dec 13 2024 at 21:52):

Summary was that we really hadn't seen demand for it in roc, but sounds fine if needed.

view this post on Zulip Richard Feldman (Dec 13 2024 at 22:42):

yeah I'm default leaning towards not having 1-tuple syntax, based on what we've discussed so far, but it's conceivable that the tradeoffs could change in the future

view this post on Zulip Sky Rose (Dec 17 2024 at 14:14):

I just proposed a way to resolve this ambiguity in #ideas > Change tuple syntax from () to {}?


Last updated: Jul 05 2025 at 12:14 UTC