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
func
is the type of a singleton couple who's only element is a function that takes types x
and y
and returns z
func
is the type of a size two tuple, the first of which has the type x
, the second the type y->z
.I think functions in tuples require parens
So that is not ambigious. It can only be a function
That is at least my understanding of the syntax today
For that to be a tuple, it would have to be tup : (x,(y->z))
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
interesting
Also, someone else with more parser knowledge may jump in later and correct what I said above, but I think it is correct.
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
)
I don't think it should be in a tuple at all
func : (x,y->z)
should be the same as func : x,y->z
Cause we don't have 1 element tuples
wait really, why not?
Has at least a little bit of context
hmmm ok so that to
wait is that covered in can
?
My understanding there is nothing in explicitly blocks 1 tuples, we just never generate them when parsing.
but that is just a guess
@Sam Mohr or @Joshua Warner would know a lot better
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
Because MyStr := Str
will be a 1-tuple around Str
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
okay then, so then that needs to be implemented because it appears to view (T)
as a (valid) type
@Sam Mohr what do you think func : (x,y->z)
should parse to?
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...
wait can tuples even have extensions?
For that to be a tuple, it would have to be
tup : (x,(y->z))
Your suggestion is my vote
That's already how we format tuples in type signatures with functions in them
and can tuples have extensions?
Tuples can have extensions
@Brendan Hansknecht so if we wanted what you just asked about to be a function, we should remove the parens
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
But even with parens it would be a 2 argument function to me
so should (x)y
be interpreted as a singleton tuple with extension y
or just the types x
and y
I don't believe we support that syntax
Yeah we do :)
(x)y
is a tuple with an ext, but (x) y
is a type application
Oh, cool!
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
basically the code currently thinks (f,ww->p)e
is a tuple with an extension e
, but is this incorrect?
That sounds wrong to me :thinking:
The only reason we really use that at outside of signatures at the moment is for tuple getters
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
Oh wait sorry, I read that wrong
That does look correct
(for some reason my eyes glossed over the e
ext in your example)
so it should be a tuple ?
or a function, or should it be rejected?
It should be a tuple with a single element, which is itself a function.
got it
so then this diff is simply correct
Yes, that looks right to me
What change triggered that?
Interesting. I would have guess that it was something that shouldn't parse.
I guess if it parses as a tuple with a function, the formatter would add in the extra parents which is fine
Why would there be extra parens?
this one and then also this one
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
Why would there be extra parens?
function signature in tuple type requires parens
Ah; I guess the formatter may add extra parens to be conservative, but it doesn't technically have to
It'll parse fine if it doesn't, in this special case of a 1-elem tuple
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
feels like a huge inconsistency even if it can technically parse
TBH tuples are very weird at a syntactic level. As are exts.
This grammar is balancing on a knife's edge ;)
ok yep that's the only test that failed, and is now updated, pr should be ready to go in like 10 minutes
Also, to be fair, extension are changing and it will fix up this case
func : (x,y->z)
func : x,y->z
tuple_extension : ((x,y->z), ..e)
tuple_extension : (x,(y->z), ..e)
Now the parens around the function are clearly needed
Oh cool that's so much nicer to parse :heart_eyes:
also why not use one deliminator for tuples and another for just grouping (in types)
See https://github.com/roc-lang/roc/issues/7091
for Simplify Types with ..
Spread Syntax
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.
I don't think it was every discussion. People are too used to tuples being (..., ..., ...)
We did discuss maybe following python and allowing a one tuple as (...,)
Summary was that we really hadn't seen demand for it in roc, but sounds fine if needed.
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
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