Another syntax suggestion
"""
This was a bit weird:
{ record & x = 3, y = 4 }
My brain's expression parser thinks record & x = 3 is one part and y = 4 is the other. I would've preferred:
record & { x = 3, y = 4 }
"""
"""
Or keep Rust's syntax, to be consistent with type constraints.
{ x = 3, y = 4, ..record }
{ ..record, x = 3, y = 4 }
Not sure about that first one; the compiler should probably enforce the second one to make it clear x and y overwrite the old values in record. My vote is on some sort of record1 & record2 operator.
"""
supporting record1 & record2 would require a major type system change, not just syntax
I'm open to something like the { ..record, x = 3, y = 4 } idea, but a natural question that might arise is "why can't I do { ..record1, ..record2, x = 3, y = 4, ..record3 } like I can in JavaScript?"
and maybe that's a plausible idea, but now again it's more than just a syntax change
"why can't I do { ..record1, ..record2, x = 3, y = 4, ..record3 } like I can in JavaScript?"
we can leave that for later :p
well but given that it's a predictable beginner question, I'd want to be careful not to trade one beginner question for a different one :big_smile:
(not saying it's a bad idea or anything, just that if it turns out we don't actually want to support all of that, we should probably figure that out before changing it!)
for context, I actually originally had the syntax being something like that, including the flexibility to mix and match, but then when @Folkert de Vries and I were pairing on it (back in 2019) it turned out to be a lot harder to implement than anticipated
so I said "ok let's just do what Elm does after all" - with the one change of using & instead of | because | has a different meaning in Roc (e.g. | patterns)
and that's what the syntax has been ever since!
so I'm totally open to changing it, just while being mindful of the can of worms haha
What is the technical complication of record1 & record2. If we say & is only for updating. record2 must have the same or a subset of the fields of record1. The types of each field must match. Then we just copy over the data.
I would assume the main complication would be for users when they expect it to be very flexible (like in js), but it only works in a very limited use case.
suppose I put this into the repl:
\a, b -> a & b
what is the inferred type of that function?
What about a middle ground { ..record, x: 3, y: 4 }?
I like how this still uses a comma, and implies that it takes the fields from record. I guess it might be strange that x could be overwriting the value in record maybe?
yeah... the thing with that is that people will assume the spread syntax can also be used the other way around { ...first, x: 1, ...second } then the response becomes the union of all records? anyway, if this was feasible technically then I think it would be a good api. if it's not but someday will be, then it could still be a good api since we could catch the unsupported usages and explain it properly in the compiler error.
yeah that's what I think people would expect :point_up:
and I think it's a reasonable option, but I do think we should first investigate what mixing and matching where the ... goes would look like from a type-checking perspective, because if we end up deciding to implement and then immediately get asked about it, I'd rather know the answer before we even started implementing it :big_smile:
I am not worried about \record1, record2 -> record1 & record2for type inference PoV - the inferred type of this is {}a, {}a -> {}a, which is all well
However I do think that this makes the semantics look like this would perform a record intersection, which it would not, unless we also expanded the runtime semantics to allow arbitrary growing of records (which has its own set of problems, namely that it incurs a runtime cost that might be small, but may also be non-obvious). The current record update semantics are only for updates - they can't grow the record, but a syntax like F & G, especially to those familiar with TypeScript, may be unexpected
Ayaz Hafiz said:
I am not worried about
\record1, record2 -> record1 & record2for type inference PoV - the inferred type of this is{}a, {}a -> {}a, which is all well
I don't think that's the type people would expect it to infer though!
that type says both records have exactly the same set of fields
so I couldn't pass it { x: 0, y: 0, z: 0 } and { x: 1, y: 1 } because it would say I'm missing the z field
but I would expect that if I can do { x: 0, y: 0, z: 0 } & { x: 1, y: 1 } then I could also extract out named variables for both of those and have it still work
or am I missing something and that would actually work even though the extension variables are the same? :thinking:
Ayaz Hafiz said:
I do think that this makes the semantics look like this would perform a record intersection, which it would not, unless we also expanded the runtime semantics to allow arbitrary growing of records (which has its own set of problems, namely that it incurs a runtime cost that might be small, but may also be non-obvious). The current record update semantics are only for updates - they can't grow the record, but a syntax like
F & G, especially to those familiar with TypeScript, may be unexpected
I'm also open to this change, even though there would be a runtime cost sometimes (which might be surprising and potentially significant) but yeah, that's a separate discussion I think :big_smile:
I’m not sure how “{ x: 0, y: 0, z: 0 } & { x: 1, y: 1 }” would work either? Unless the proposal here is that there is a new syntax solely in the record updating case with a record literal. Because then you could not even do
r1 = { x: 1, y: 1 }
{ x: 0, y: 0, z: 0 } & r1
with today’s semantics of Roc - this is not admissible.
right, but that's a core use case for record updates right?
like { xyzRecord & x: 1, y: 1 }
so if infix & can't do that, then I don't think it would be very nice to use :big_smile:
The infix case is different in that the syntax is restricted though. You can’t put any arbitrary record there (syntactically) - you can syntactically only list certain fields that are eligible to update.
If the same restriction is applied in this proposal there is no worry from a typechecking perspective right
sorry, when I say infix & I mean record1 & record2
so not syntax-restricted
definitely { xyzRecord & x: 1, y: 1 } does not have this problem, but I think \record1, record2 -> record1 & record2 having the type {}a, {}a -> {}a would have this problem
well, if the goal is to arbitrarily update records with any fields, I do not see how we get there without allowing the runtime to change records to augment additional fields :joy:
do you think there is another way?
oh I agree - I assume if we wanted to allow record1 & record2 we'd have to allow the runtime to add fields
but my original point was that record1 & record2 couldn't be just a syntactic change, but rather would have to be a type system change too (and code gen)
with the specific semantics of wanting to arbitrarily extend records I agree. with the current semantics it could be made to work though even if it’s maybe not the most intuitive behavior which i think we also agree on.
I still think you could technically make record1 & record2 restrict record2 such that it is the same fields or a subset of the fields of record1. Then you can update, but it can never grow.
That said, I do think that would be a terrible confusing interface especially with context from other languages, I just think it is technically possible.
I'm starting to think neither the current syntax nor any new proposals are good because none are self-evident to newcomers.
I propose:
Record.update coords { x = 3, y = 4 }
more verbose, but also self-evident, simple and familiar
Although I don't think we can express the type signature for that currently, which makes me wonder how the compiler handles our current record updates
I think that’d be typed the same as (&) if it were to be introduced, it’s just the non-infix version
Is it possible to spell out the type of Record.update? Or is this (&) something that already exists and has a special type?
Is it possible to spell out the type of Record.update?
Not currently, no.
Or is this
(&)something that already exists
The (&) mentioned by Agus refers to record1 & record2, which is not supported now. If we want to implement it, I expect it would be an infix notation that would desugar to Record.update under the hood.
Right now we support { user & email: "blah" }, but I don't know how that is handled in the compiler.
I think record update is fundamental to a number of applications, so i don't really prefer it as explicit, but i totally see the argument.
I think record update is a simple enough syntax that you just need to learn once.
Also, if you make it a function, how do you stop:
Record.update record1 record2 and all of the complications that it leads to? Discussed above as issues with record1 & record2. I think that would be even more confusing to end users. This function takes 2 records, but i have to define the second record inline or it errors out. I think that makes more sense to error out in a custom syntax like we have with &.
Yeah, I agree
on elixir, we have both explicit and non-explicit record updates and we basically never use the non-explicit ones since we can't compose then as part of pipes, etc.
we also have an utility for nested updates but trying to think about it a way that makes sense with types is a bit weird... so I wouldn't expect it to be possible in Roc.
Record.update : { ..a, ..b } -> { ..a } -> { ..a, ..b }
Seems like a pretty nice type signature for things like the phantom builder pattern :sweat_smile:
Elixir is dynamic and allows for record update to add fields?
the non-explicit syntax doesn't allow that. the field needs to be something that is already in the map. I don't know the reason. but yeah - the explicit allows new fields to be added. structs are just maps in elixir.
I was only talking about the usefulness of something like Elm's update syntax vs an explicit function. The explicit function might provide better ergonomics in the end... but I understand the problem related to type signature.
Last updated: Jun 16 2026 at 16:19 UTC