With structural typing and field punning I find myself rarely using tuples in Roc. Idk if it’s just me, but would there be an uproar over just removing them completely? You could still have a Pair a b : [Pair a b] alias
They were added specifically because that world wasn't great T a b c
In that world you also can't do tup.6. you have to pattern match
But I guess with structural typing, what are the advantages of Tuples?
And I've been a Tuple guy since my Scala days. but with no-ceremony records, it seems to be better to force the aggregate to carry the semantic information about the items forward
They have all the same advantages as records, but with less verbosity and no names.
I guess the counter-argument could be that "naming things" is one of the hard problems of CS and you are know forcing someone to name the fields
Especially nice when you expect the user to name things differently than your API would name them
I think the verbosity only increases in two cases: when an item is a literal, or the variable that hold the value for the item is different than what you would name a field for it
Side note, I would roll my eyes at any API that asked me for a tuple instead of naming things - naming things is one of the biggest things APIs do.
And in every destrtucture after a function call where the user wants to name the variable differently cause the chosen names from the API can't be used repeatedly and are not specific enough
I'm down to start a separate thread to crowdsource lists of strengths for each primitive :)
(I seriously want those lists) (no pun intended) (no "field punning" intended? I'm only now learning this phrase)
That might be a good call @JanCVanB I just went through all the builtins and unless I missed something, we don't have a single tuple as either an arg or a return value
Would simplify the parser, AST, and probably Can and mono as well. Though, I feel like I'm betraying an old friend here
Again, not a hill I'm ever gonna die on, but it's worth a full discussion
Most of the builtin where designed before tuples and the roc std lib often chooses to be overtly specific due to the expected user base. I don't think it is a good case study for tuples.
A simple example. Replace has no reason to return a record. The types guarantee they will never be confused and are exceptionally clear. On top of that ~100% of the time, the fields will get renamed because list and value are terrible variable names.
Personal opinion, but I'd rather - as a code reviewer - to see |> .list rather than |> .0 even if it doesn't matter (as much) in an Editor with a LSP.
Or even { list: whatEver } over (whatEver, _) =
You would rather see { list: nextCats, value: oldCat } = List.replace cats 7 newCat
Than
(nextCats, oldCat) = List.replace cats 7 newCat
That's how I view all language syntax choices - what kind of syntax makes things as unambiguous as possible without an LSP or having to consult the documentation. But maybe because I use a lot of languages and I don't always remember the exact details of every API in all of them, and I spend a lot more time reviewing code than writing (being an Architect is fun at Fortune500)
I never find myself using the old value, but good point if you do use it often
You would not use replace if you weren't using the old value
You would just use set
Also, I think it is an important note that in the roc std lib there are very few returned records. And returned records are the only place that might be a returned tuple instead.
Not if you found replace doing AOC, found it did what you wanted, and never saw set :rofl:
Brendan Hansknecht said:
Also, I think it is an important note that in the roc std lib there are very few returned records. And returned records are the only place that might be a returned tuple instead.
That's a good call it. Do we agree that tuples as args are a bad idea?
:joy:. Hopefully a linter can catch that in the future.
And I'm not saying my opinions are correct. I make bad choices sometimes. For instance, I was going to make DEC64 the only number type in my language
That's a good call it. Do we agree that tuples as args are a bad idea?
Yes. We already have those. They are called regular args. No need to nest them generally. And if you do nest them, naming is great for clarity.
Now I'm really curious how much I actually use tuple. I think it is most useful for a return type from a local function that is used everywhere in a module. Also probably should be a tuple of unique types so they can't be mixed up easily.
Cause constant renaming becomes a hassle and the function is so pervasive in the module that you have to learn it anyway.
I used it in one of my AOC puzzles for representing both Point and Delta, and boy did I regret that
42 messages were moved here from #ideas > Remove the need for backslashes for lambdas? by Brendan Hansknecht.
{x,: 0 y: 5} and {dx: -1, dy: -1} feels so good compared to (0, 5) and (-1, -1) even if more typing
Sure, but that happens with plain number types too.
It isn't really tuple vs record
It is more tagged type vs untagged type
Just happens that record field names are a form of single tag
Yeah Point U64 U64 would have worked
But the names to each field is very helpful
@Brendan Hansknecht @Richard Feldman What do y'all think of keeping the first 10 messages in this thread (up through @Anthony Bullard's "ducks") in the backslash thread?
You didn't ask me, but I think that makes sense
That's when the conversation took a real detour
Thanks for the intriguing detour :smiling_face:
sure
relevant thread: #ideas > tuple syntax - this was from back before Roc had tuple syntax, and led to deciding to introduce it
I'm also happy with them moving there.
(Sidenote: Besides the iOS app randomly freezing, I really love Zulip and it's ability to manage conversation like that)
Would be really nice if I could move a range of messages (or checkbox select a handful of messages). The only ranges it allows are from one message all the way to the end of a thread.
Hope they can add that. We are on a different open source chat platform at work, and I would welcome moving to this
Sorry, I guess I could move these to a new "Off topic > Zulip glazing" thread :rofl:
Also, this may be a good reference on some things tuple. This is false interpreter. This PR upgrades a mix of things, but one specific thing it upgrades is from the old T a b syntax to (a, b): https://github.com/roc-lang/roc/pull/7369/files#diff-993d5a4d31fa23173a38bd6b7070bff1dd7267ade62e7f852467616052601d52
Yeah the one strong suit of tuples is "returning just two things that aren't really related, but I created as part of this function"
But it's a question of is that specific case worth the syntax budget (and slight additional compiler complexity)? Not saying yay or nay, but I think that's the question
Richard always says he wants Roc to have as few, simple primitives as possible
When we compile down to LLVM IR, is a union with a single tag with a payload of two values end up just being a two field struct?
yeah tuples and records and single-tag unions all compile to the same thing
tuples and single tags are less efficient than records though (right?) I would have to double check, but I don't think they reorder fields...though if tuples desugar to records they must. So maybe only single tags are less efficient.
nope, they're all identical
they all do the same reordering based on alignment
never realized
I mostly use tuples in when statements when I want to pattern match on multiple things at once.
So as an intermediate thing.
Also when returning multiple values in a lambda for use in the rest of my function. So very temporary.
I started #contributing > Strengths of each primitive/builtin Roc type :smile:
I used tuples a lot in Python, and I think I've never used tuples in Roc. I used to wonder if I was missing out, but now I'm feeling better like I'm not!
In my mind tuples are required because:
(T a b), (Tup a b), (Tuple a b), or (Pair a b) (known from experience in roc before tuples)(a, b, c).So it isn't really tuples vs records when looking at the larger ecosystem. People are lazy (including myself in many cases) and many will not default to records if tuples don't exist. They will default to their own naming of a tag for tuples. The names will not be consistent.
yeah given that we already did the experiment of "what if Roc didn't have tuples?" and concluded based on that experiment that we should add tuples to Roc, I think any idea of re-running that experiment would need to begin with a plausible explanation of why it might end up with a different conclusion the second time :sweat_smile:
Seems like tuples serve two functions better than alternatives:
I'm not sure where else they are considered "the right tool for the job"
when pattern matching on multiple things at once
also the first one can be "aggregate of data returned by an expression" - (a, b) = when ... is also pretty common
another one is matrices and vectors
Gleam handles that case by allowing when patterns to have multiple values, but that's a pretty fancy feature that you can solve by just keeping tuples
yeah exactly
same with "multiple returns"
Side note, I just noticed that RustGlue.roc fails to generate glue for tuples.
Foo : { a: U64, b: U64, c: U64, d: U64 }
...
🎉 Generated type declarations
...
#[derive(Clone, Copy, Default, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[repr(C)]
pub struct Foo {
pub a: u64,
pub b: u64,
pub c: u64,
pub d: u64,
}
Foo : (U64, U64, U64, U64)
...
thread 'main' panicked at crates/glue/src/load.rs:342:32:
not yet implemented
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
(I doubt this is permanent, but I literally just hit this and it seems relevant.)
Richard Feldman said:
another one is matrices and vectors
Has there ever been discussion of adding arrays to Roc? I don't do much matrix math, but when I do it's almost always with arrays
Arrays work with index operators, whereas tuples require passing things around by hand.
yeah we talked about arrays in the context of tuples
I'll look at that old thread you linked, then
I still kinda want arrays in roc, but I can't think of a way to make them play nicely with the list ecosystem. And I don't think they are worth splitting the ecosystem.
Sam Mohr said:
I'll look at that old thread you linked, then
me: "Let's see what they said in ages long past..."
me in 2022 expressing optimism about tuples
me: "Well well well, if it isn't my old rival, me."
It would make so many matrix and geometric problems much nicer (AOC brought this up for me). But would have to be a built in type with language support for that to be true
Inb4 Rust's const generics??
I don’t think you can weigh syntax and language features in a vacuum
What things are hard to implement if you add the feature (or keep it). What syntactic constructs are off limits? Can the feature be replicated in a way that is unergonomic to the inverse ratio of how free it is/will be used?
I see it in Tc-39 all the time us kicking ourselves for some feature because now we can’t do 1-3 other things (or they have be done in a worse way)
Though in JS we can’t undo ANYTHING once it’s on the web
Also, for matrices and mathematic or scientic computing, I don't think arrays are enough
Like if I gave you arrays today, your matrix experience would most be the exact same
Cause either the matrix could be dynamic and built on list, or it could be static and built on tuples
The actual matrix API would be exactly the same if arrays existed.
So I would say that arrays are a red herring currently.
The end user's API would be the same, but array dev would be a good deal simpler than tuple dev
I agree that a single use case for arrays is not enough to consider adding them
Would be equivalent ergonomics to a list version.
Especially when talking about something like AOC, but also in general, I would want to see list based matrix libraries with decent use before considering adding arrays
Arrays are strictly a potential perf improvement and do not affect ergonomics at all.
I’d be surprised if that’s the case. Unless we would just have a Array a type
But I agree we probably don’t need them
And it’s a distraction to this discussion, which already was a distraction to another :joy:
Ah yeah, forgot about numerics in the type system to allow for removing the bounds check st compile time. Which not only helps perf, but also improves some ergonomics. Though in a matrix library can just always use listGetUnsafe that crashes one failure to load. And that gets back the same ergonomics for the most part
Emboldened by the shared disdain for tuples-as-args, an idea struck me! I need some perspective on it from you, since it introduces just a little magic. Disallow tuples as function parameters.
I almost exclusively use them as
(a, b) = myLocalExpression c d
# or
when (a,b) is
(X, Y) -> ....
There would be just some instances where that would be quiet annoying. Like we were planning to remove all the List.somethingWithIndex functions, in favor of List.enumerate |> List.something after some optimizations are in place. Assuming List.enumerate returns a list of (element, idx) tuple, piping is no longer an option.
# can't do
mylist |> List.enumerate |> List.map \(element, idx) -> ...
I don't think this come up often, most of the time I would prefer if the return value was destructured into variables. But with lists, they would come up, so:
Tuples as parameters aren't allowed, but tuples as arguments are. We would auto-destructure, or "spread" them, so the previous example would be
mylist |> List.enumerate |> List.map \element, idx -> ...
The worst form of this would come up with nested tuples (at which point you have more issues than this feature) where this would be valid code:
make_rgba_more_red = \r, g, b, a -> ...
#later in use
make_rgba_more_red ((0, 0, 255), 100)
# same as
make_rgba_more_red 0 0 255 100
This would free up the (a, b) -> ... lambda syntax (even thought I prefer our current one), but more importantly, would eliminate bad use of tuples.
I think golang only supports tuples as function return arguments. I'm not sure if it's a technical decision, or pragmatic one to push people to always use records except for multiple return values from functions. Googling to see if there's any documentation around that design choice...
I guess the downside to "only in return position" is you have to figure out how to pipe output of one function as input to next. Again, golang only supports lowering (raising? coercing?) to fn args when the types match exactly. so
func g() (int, rune) { return 4, "a"; }
func f(i int, r rune) { ... }
func h(i int, r rune, j int) { ... }
func main() {
f(g()) // this is fine
//h(g(), 5) // this will not compile
i, r := g() // need to destructure first
h(i, r, 5)
}
roc pipeline equivalent might be
g = \() -> (4, "a")
f = \i, r -> ...
h = \i, r, j -> ...
main = \() ->
g |> f
# or
main = \() ->
g |> \i, r -> h i r 5
so pretty similar
Also more to the point of this thread, golang's design seems to support @Brendan Hansknecht 's desire for nextCats, oldCat := List.replace cats 7 newCat while also supporting @Anthony Bullard 's desire to not have tuple types.
Oh, just realized it misses the when a, b, c ... pattern matching case.
Another current use of tuples is Dict.toList which is currently Dict k v -> List ( k, v ). In my opinion, it would not be great if it gave you structs with key and value fields, in this case, you almost always want to name the key and value yourself
so
Dict.toList catStuff |> List.map \(cat, stuff) -> ...
vs
Dict.toList catStuff |> List.map \{key: cat, value: stuff} -> ...
I guess you can name it yourself, but tuples avoid some typing
maybe not a great example of "prior art in mainstream programming langs", but jq has to_entries which is essentially Record -> List {key: Str, value: Value}. The verbosity doesn't really bother me, because for my uses to_entries is necessary, but rare.
As was mentioned above, I don't think fully removing tuples is a reasonable option. We had the world without tuples before. You will start seeing various tag based tuples without consistency. T key value or Tup key value or Tuple key value or Pair key value. That world is much less convenient than the world today with tuples.
yeah I think the answer to the question posed by the thread is just "yes" :big_smile:
I agree. I’m going to say, as “OP” that the resolution is YES. And then mark as resolved
Anthony Bullard has marked this topic as resolved.
Last updated: Jun 16 2026 at 16:19 UTC