Using Elm's record update syntax (i.e. using the the pipe operator and equals sign) seems superior to Roc's current syntax for a few reasons:
Given the way that I believe most languages use the '&' or '&&' symbol, when I see the '&' symbol in Roc's record updates, my brain wants to incorrectly parse the '&' as tightly binding the thing before and immediately after, as in:
{ ( existing_record_name & updated_field_name: ) updated_field_value }
The '|' looks visually simpler (it's just a line) and more distinctive than '&' to the words around it, ( I think this is probably an objective thing). For these reasons, the '|' separates the two sections more clearly, i.e. it's a little easier and more pleasant to parse.
I don't mean to be too strongly opinionated about this, I'm probably not fully appreciating the reasons why Roc diverges from Elm syntax here. I do at least see the value of using "when x is" instead of "case x of", even though I would probably have gone with "case x of" for consistency and using people's existing knowledge. With 'when x is', at least there is a clear win as it's more intuitively meaningful.
Any thoughts on this?
Just a note, there are 3 cases to deal with here:
record = ...
newVal = ...
field2 = ...
{ record & field: newVal, field2 }
val = ...
field2 = ...
record = {
field1: val,
field2,
field3: #some complex multi-line expression.
}
{ field1: val, field2 } = record
# can also be used in pattern matching and lambdas
The | for updates deals with record updates. What about the other cases?
In those other two cases, we are also not using & today, right?
Yes, but they mentions problems with : as well. So trying to understand that combination.
So I just want to see equivalent code examples for the core cases here
Ah yes, gotcha :+1:
@Brendan Hansknecht re: "What about the other cases?"
I guess, to be more Elm-like ':' would become '=' in the other cases you listed.
So here is the proposal typed in examples:
record = ...
newVal = ...
field2 = ...
{ record | field = newVal, field2 }
val = ...
field2 = ...
record = {
field1 = val,
field2,
field3 = #some complex multi-line expression.
}
{ field1 = val, field2 } = record
# can also be used in pattern matching and lambdas
Looking at this I basically have zero issue with & being changes to |.
Changing : to = on the other hand, looks really weird especially in record destructing (I guess it is now read backwards unless we flip the args in just that case).
Flipping looks better:
{ val = field1, field2 } = record
# can also be used in pattern matching and lambdas
I think I like this, but it's almost like an "uncanny valley" thing. It's so close to normal variable assignment...
I like the direction of this proposal but feel like there's something missing!
Also, we have to remember that many of these cases can be formated on multiple lines and have multiline sub expressions.
but yeah, reversing definitely looks better, but with more obscure names would probably be confusing (especially in a lambda with an type inferred struct)
When I read ':' I think of a type declaration first, then I think, oh ya, this is in the context of a record, it doesn't mean that here. Type declarations and records are such central features of the language, they would ideally have a different syntax! Within the context of Roc or Rust, it's a tiny bit jarring to see such a central symbol being used with a starkly different meaning. The equals symbol seems at least equally meaningful and much less conflicting.
in Roc today:
= is a pattern: is a record field declaration: is a type declarationChanging it to = would create syntax ambiguities. Sometimes = would be preceded by a pattern, and other times by a record field label that doesn't introduce anything into scope, and there would be no local syntactic difference between them; you'd have to search for context clues in the surrounding code to tell which it was.
Separately, although = would be more familiar to those who are accustomed to ML language family syntax (which uses = for record field declarations), this would be at the expense of being less familiar to everyone else (i.e. the vast majority of programmers, since other languages overwhelmingly use the exact same syntax Roc does for record fields).
I see this as a downside, all else being equal. If there's a specific benefit to the way ML syntax does something compared to other syntaxes (e.g. spaces for function application makes writing DSLs nicer, having standalone type annotations is nicer in a variety of ways), that's a good reason to use ML syntax, but I don't think "it's more familiar to ML programmers at the expense of non-ML programmers" is helpful to a language that aspires to be used widely by programmers in general. :big_smile:
I don't have particularly strong feelings about & and | except that | in programming typically means "or"
for example, in Elm you have type Bool = True | False - there it does not mean "with," it means "or"
likewise, || means boolean OR
in Roc, we use | as a delimiter between alternative patterns, e.g. Foo | Bar | Baz -> ... in a when - again, | means "or"
I think it's less important that in mathematics | can mean "with" than what it usually signifies in programming - which is "or"
I always took| to mean "union", and this makes it consistent in all of the following usages
There is an interesting third option by the way
Some languages allow the usage of @ to bind to a larger part of a pattern while also constraining its details further
e.g. when mypair @ (1,x) is
But you could re-use the @ to also work inside record updates
Meh, maybe this is actually confusing
Despite my comments, the current Roc syntax for records doesn't seem "bad" to me...
For me the slam dunk of web development would be to be able to use the same Elm-like language on the front-end and back-end.
So frankly, I personally want Roc to be (as much as possible) Elm on the backend, and I think that is part of Roc's ambition. Any differences Roc has to Elm will have some cost in terms of quality of life for people like me who want to make Elm + Roc web projects.
I do understand that there is a bigger picture here of tradeoffs vs. my narrow focus on Elm + Roc web projects, but I think the appeal and value proposition that Roc offers to web developers who want to work with Elm deserves careful consideration.
Having said that, If it's not possible to follow Elm's syntax or it makes sense to accept that we are making that compromise of not following Elm's syntax here (it is a compromise for people like me) then so be it. I can live with that. Having Roc carry forward Elm's other qualities is more important to me (qualities like simplicity/approachability, reducing run-time bugs, a thoughtful approach to mutation, amazing error messages, good tooling etc). :smile:
Richard Feldman said:
in Roc today:
- always, what comes before
=is a pattern- always, an identifier followed by a
:is a record field declaration- always, an identifier followed by a blank space and then a
:is a type declaration
The tutorial doesn't seem to follow this convention... I've opened an issue (Tutorial has spaces before colons in records where they shouldn't be)
This is cool. I am thinking of adding to tutorial. Would it make sense to live with operator desugaring? Maybe we could expand this into a more general syntax quick reference annex or something?
Other quick references might include and link to relevant descriptions;
@ :=Frac 3u64 Dec<-# ##{} : ?:-> \the other thing that comes to mind is the .property and &property functions (tho I've only seen the latter in the action document - is it already available as a setter function?)
I'm not sure, but you should be able to check in the repl
yeah it's not implemented yet, just in the idea phase at the moment
Should there be a corresponding &1 setter syntax, for tuples?
I honestly never thought about it - could be!
Last updated: Jun 16 2026 at 16:19 UTC