What if we used curly braces for tuples instead of parens?
old: ("abc", 123)
new: {"abc", 123}
Pros:
Cons:
There would be a couple new ambiguities with records, but these are minor compared to ambiguity with parens for grouping:
{} where some have keys and some don't.{} could be an empty tuple or an empty record, but I don't think it matters.I also thought about this. But I think this is not possible.
Here is an example:
hello = "hello"
world = "world
value = {hello, world}
With the current syntax, this is a record of type {hello: Str, world: Str}. When a tuple also uses {}, then it also could be of type {Str, Str}
Yeah, you can't do this and ALSO have field punning in records. And the advantages of punning outweight any sort of benefit one could imagine from changing the tuple syntax. And the only other brace pair that is unused in the language is <> and I don't think anyone wants to use <"abc", 123>. The other option is to do what Elixir does and use a sigil for differentiating between records and tuples:
Tuple: {"abc", 123}
Struct: %{hello, world} or #{hello, world}
I think the sigil approach makes tuples more ergonomic than records, which is not what I think we want to do
I guess you could flip the game, and make a tuple have the sigil, but I've never seen that.
And I wonder if there really is ambiguity with parens for grouping, since we don't allow for 1-tuples (which IMO don't make a lot of sense)
You see a paren, you parse a list of expressions ending with a paren. If you have a single expression - it's just a group, more than one - it's a tuple.
I think the only real advantage of getting tuples off of the () syntax is getting a more familiar lambda syntax for when we move to parens-and-commas style application
There really is ambiguity with grouping, as shown in the thread I linked. They managed to figure it out in that case, but even if the parser has a rule to follow, it can still be surprising for the programmer.
Joshua Warner said:
TBH tuples are very weird at a syntactic level. As are exts.
This grammar is balancing on a knife's edge ;)
Good point about the parens function calling syntax, that's a 3rd use of parens. I think it's less likely to cause ambiguity but it does still add confusion.
I forgot about pinning though, you're right that means we need slightly different tuple and record syntaxes.
The sigil approach seems reasonable but adds additional weirdness. It still suggests that records and tuples are related, which is good.
Though a sigil could also resolve the ambiguously even with parens. Like if tuples were #("abc", 123). (Edit: Gleam has this tuple syntax)
Someone in the other thread also mentioned using a different delimiter (instead of comma) for tuples, like ("abc"; 123) or {"abc"; 123}, which would work for disambiguating from grouping with parens or pinned records with braces, but I think it'd be more confusing than a new bracket.
Edit: Rejected
destructuring in patterns is the bigger deal here:
{ x, y } ->
I understand the appeal of the idea, but personally, I look at that one line of code and instantly conclude that curly braces cannot be overloaded for both records and tuples :big_smile:
also I think using semicolons for collection delimiters would be a huge mistake. That sort of aesthetic choice is like beginner repellent. :sweat_smile:
it might have been reasonable in the 1970s but today the most common reaction is "ew, what is that"
I just caught up with the other thread which also discussed different sigils and brackets for tuples https://roc.zulipchat.com/#narrow/stream/304641-ideas/topic/Remove.20the.20need.20for.20backslashes.20for.20lambdas.3F/ I thought this was an original idea but I guess not!
I do think there's a lot of reasons for changing the tuple syntax that have popped up recently, but there's no clear frontrunner for what to change to.
Random idea
{0 x, y } ->
However, I'm still unconvinced that field punning can't be preserved even if they both use { } - that just seems like a distinction that the compiler can handle, writers will understand, and readers won't notice.
A prefix sigil seems like the best option still, but I'm personally not convinced that using a different syntax is a positive change for tuples in the first place.
Does anyone have a full app example in mind that would cover 80% of { } use cases to discuss implementation options?
The compiler infers varied types for numbers based on where they come from and how they're used, and this feel similar.
Why zero?
I see tuples as numeric-index-labeled records.
Ex: "Compilation Error: This record was created unlabeled, but here you reference a labeled field. Either reference this field by numeric index or add labels during its creation."
{ x, y } # labeled record
{ x, "bar" } # unlabeled record
{ "foo", "bar" } # unlabeled record
{ x: Str, y: Str } # labeled record type
{ Str, Str } # unlabeled record type
And { x: 123, "bar" } is just invalid, presumably?
And if we really want an unambiguous way to create an inline unlabeled record from other defs, that could be just three characters extra
{ 0: x, y }
but it could also just be contextual.
(The syntax above inspired my {0 sigil idea, but I'm not advocating for that right now.)
I'm struggling to think of an example where we'd strongly want a record constructed purely from other defs to be unlabeled lol
I feel like it would add more rough edges where a beginner hits syntax that is frustrating or confusing with unusual wording (unlabelled record vs tuple and needing 0: in some cases).
To a new user, once they figure it out, I assume their question will be: "why this instead of normal tuples?".
I'm struggling to think of an example where we'd strongly want a record constructed purely from other defs to be unlabeled lol
Most common is probably pattern matching. Also happens in return types on occasion
We discussed in another thread that unlabeled records are poor choices for API values and function parameters, because naming things there is good, so I guess the 0: injection would be so rarely needed that we might not even teach it.
For return types, whether its labelled or not is irrelevant, since you immediately destructure it.
One is just more verbose for little gain in return types.
{ x, y } = f x # f happens to return a labeled record
{ a, b } = g c # g happens to return an unlabeled record
And if you want to return a tuple, you probably need to learn about 0:
In your example, the first one only works if you want those exact names. It is relatively common to change the name (especially due to collisions from calling the same function more than once)
So then you'd advocate for writing the function like g, which is fine
g = \c ->
a = "foo"
{ a, "bar" }
You'd only need the 0: if every field is already named, like
g = \c ->
a = "foo"
b = "bar"
{ 0: a, b }
Footgun?
I don't think it is a footgun....too strong of a word. Just an inconvenience that new users will hit and be confused by.
Will feel unnecessary when it could have been fully avoided by not overlapping with records
If this is really the only encouraged use of tuples though, do they deserve their own primitive and symbols?
They seem to fill a tiny gap in the language itself, which we could patch with something that already needs to exist elsewhere.
I believe every other solid use of tuples wouldn't conflict with labeled record field punning, like nvm, but my question standswhen { foo, "bar", Baz } is (wait, idk if that's even real lol)
They fill the gap left by arrays as well
I think Roc has been successful so far in prioritizing simple, easy-to-learn features that may overlap rather than prioritizing clever solutions. These two approaches to keeping Roc "simple" point to different definitions of simple, the first meaning "comprising low-complexity components", the second meaning "small number of components". I think the first definition is a better to strive for
So fair
So if everyone and their grandma knows what a tuple is already, we need something easier to learn than them to fill the multiple holes that tuples plug IMO
In the end, if Roc has 2-10x more parens and inline tuples become harder to read, I suppose that isn't a bad thing if inline tuples are conventionally discouraged - gap fillers only.
JanCVanB said:
If this is really the only encouraged use of tuples though, do they deserve their own primitive and symbols?
see :wink:
I'd be open to putting a warning on using tuples where they shouldn't be used (I can only think of assigning tuples to a value without destructuring them as an undesired usage), but they fill a lot of gaps, so we seem to need them
Yup, and above I only wanted to explore the difference between "need" and "which symbology should we overload"
Because this seems to require either a sigil or Roc's sole overloaded bracket
I'm trying to take that into account. You and @Luke Boswell are ideas people, and I think Roc only got to where we are because of people like you. I don't want to depress ideation, it's very important. I just don't think the underlying problem is there in this case, otherwise I'd be right there with you
If only there was a fourth ASCII bracket - why didn't they foresee four collection-y types lol
)new, tuple, syntax(
{| oh, yeah, bebby|}
Ruby and Rust use pipes for lambdas, which is essentially a way of using them as brackets
not saying we should do that, just that there's predecent for it
as the "fourth bracket"
I never want to be "that guy" who asks for table flips for minor inconveniences, so please (and thank you) continue to professionally hold our ideas to high standards.
Richard Feldman said:
as the "fourth bracket"
Hope you never need to nest
Also, general statement to anyone, if I'm ever "that guy" for any definition of "that", please let me know - DMs open too.
JanCVanB said:
Also, general statement to anyone, if I'm ever "that guy" for any definition of "that", please let me know - DMs open too.
I think you’ve mostly been that cool guy and that’s it
I really like the look of the | character, and it's just as easy to type as parens (you have to hold shift for it). It wouldn't be a bad change IMO for function args, meaning we can leave tuples as-is
A little dash of Ruby
or Rust :crab:
I was appealing to maybe Richard’s nostalgia (maybe that has negative weight)
I know NRI was a Rails app once upon a time
I hate Ruby, I'll be "that" guy
Though of course it has some nice stuff
I actually started #ideas > ✔ lambda syntax awhile back about pipe syntax for lambdas; you can read the thread to see how that went :big_smile:
tradeoffs might be different with parens-and-commas, not sure :shrug:
Thank you Richard for being this forum's thread memory.
So to summarize, options I've seen are:
(), navigate the ambiguities and conflicts with other uses of parens.{}, have some way to disambiguate with punned record field names, like requiring {0: in ambiguous cases.{}. #{}, %{}, ~{}, |{}, {| |}or something like that.#{}, which I read as "A record, but with numbers as keys, and # is the number sign".(). %(), ~(), :(), or something like that.#() is what Gleam uses.<>, the fourth bracket type.Note: # currently is the comment prefix
ah, shoot :face_palm:
I agree that the numeric implication makes it my frontrunner as well, but for this issue
I agree but also it implies hashing, which nope
Last updated: Jun 16 2026 at 16:19 UTC