Stream: ideas

Topic: Record Syntax


view this post on Zulip Matthias Toepp (Jul 24 2022 at 14:38):

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:

  1. 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.
  2. 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 }

  3. 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.

  4. I pronounce and think of Elm's '|' (or Roc's '&') symbol in the context of a record update as "with". The '&' (which I read as 'and') in my mind implies something is added on. The idea that something is added on seems misleading if the field is being replaced.
  5. I'm no mathematician, but I did manage to basically learn Elm's syntax of the pipe symbol for updates in high school math, i.e. it's a well established syntax.
  6. For people who want to use Elm along with Roc, using Elm syntax for records would give a nice experience of greater consistency.
  7. It seems generally simpler and more consistent for Roc to follow the syntax of Elm rather than Rust or Javascript. This is not just about theoretical purity or something....it means that when I sit down to write Roc, I can can just mentally switch to the mode of writing elm-like syntax, and I'll have it about right (even if I never write any Elm).

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?

view this post on Zulip Brendan Hansknecht (Jul 24 2022 at 14:57):

Just a note, there are 3 cases to deal with here:

  1. Record update
record = ...
newVal = ...
field2 = ...
{ record & field: newVal, field2 }
  1. Record creation with differing variable and field name
val = ...
field2 = ...
record = {
    field1: val,
    field2,
    field3: #some complex multi-line expression.
}
  1. Record destructing with variable name binding
{ 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?

view this post on Zulip Qqwy / Marten (Jul 24 2022 at 14:59):

In those other two cases, we are also not using & today, right?

view this post on Zulip Brendan Hansknecht (Jul 24 2022 at 14:59):

Yes, but they mentions problems with : as well. So trying to understand that combination.

view this post on Zulip Brendan Hansknecht (Jul 24 2022 at 15:00):

So I just want to see equivalent code examples for the core cases here

view this post on Zulip Qqwy / Marten (Jul 24 2022 at 15:00):

Ah yes, gotcha :+1:

view this post on Zulip Matthias Toepp (Jul 24 2022 at 15:08):

@Brendan Hansknecht re: "What about the other cases?"

I guess, to be more Elm-like ':' would become '=' in the other cases you listed.

view this post on Zulip Brendan Hansknecht (Jul 24 2022 at 15:14):

So here is the proposal typed in examples:

  1. Record update
record = ...
newVal = ...
field2 = ...
{ record | field = newVal, field2 }
  1. Record creation with differing variable and field name
val = ...
field2 = ...
record = {
    field1 = val,
    field2,
    field3 = #some complex multi-line expression.
}
  1. Record destructing with variable name binding
{ field1 = val, field2 } = record
# can also be used in pattern matching and lambdas

view this post on Zulip Brendan Hansknecht (Jul 24 2022 at 15:18):

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).

view this post on Zulip jan kili (Jul 24 2022 at 15:24):

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...

view this post on Zulip jan kili (Jul 24 2022 at 15:25):

I like the direction of this proposal but feel like there's something missing!

view this post on Zulip Brendan Hansknecht (Jul 24 2022 at 15:31):

Also, we have to remember that many of these cases can be formated on multiple lines and have multiline sub expressions.

view this post on Zulip Brendan Hansknecht (Jul 24 2022 at 15:32):

but yeah, reversing definitely looks better, but with more obscure names would probably be confusing (especially in a lambda with an type inferred struct)

view this post on Zulip Richard Feldman (Jul 24 2022 at 16:59):

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:

Changing 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:

view this post on Zulip Richard Feldman (Jul 24 2022 at 17:00):

I don't have particularly strong feelings about & and | except that | in programming typically means "or"

view this post on Zulip Richard Feldman (Jul 24 2022 at 17:00):

for example, in Elm you have type Bool = True | False - there it does not mean "with," it means "or"

view this post on Zulip Richard Feldman (Jul 24 2022 at 17:00):

likewise, || means boolean OR

view this post on Zulip Richard Feldman (Jul 24 2022 at 17:00):

in Roc, we use | as a delimiter between alternative patterns, e.g. Foo | Bar | Baz -> ... in a when - again, | means "or"

view this post on Zulip Richard Feldman (Jul 24 2022 at 17:01):

I think it's less important that in mathematics | can mean "with" than what it usually signifies in programming - which is "or"

view this post on Zulip Qqwy / Marten (Jul 24 2022 at 20:48):

I always took| to mean "union", and this makes it consistent in all of the following usages

view this post on Zulip Qqwy / Marten (Jul 24 2022 at 21:11):

There is an interesting third option by the way

view this post on Zulip Qqwy / Marten (Jul 24 2022 at 21:12):

Some languages allow the usage of @ to bind to a larger part of a pattern while also constraining its details further

view this post on Zulip Qqwy / Marten (Jul 24 2022 at 21:13):

e.g. when mypair @ (1,x) is

view this post on Zulip Qqwy / Marten (Jul 24 2022 at 21:13):

But you could re-use the @ to also work inside record updates

view this post on Zulip Qqwy / Marten (Jul 24 2022 at 21:14):

Meh, maybe this is actually confusing

view this post on Zulip Matthias Toepp (Jul 25 2022 at 08:47):

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:

view this post on Zulip Matthias Toepp (Feb 15 2023 at 12:27):

Richard Feldman said:

in Roc today:

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)

view this post on Zulip Luke Boswell (Feb 16 2023 at 06:20):

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;

view this post on Zulip Georges Boris (Feb 16 2023 at 10:21):

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?)

view this post on Zulip Brian Carroll (Feb 16 2023 at 12:45):

I'm not sure, but you should be able to check in the repl

view this post on Zulip Richard Feldman (Feb 16 2023 at 22:10):

yeah it's not implemented yet, just in the idea phase at the moment

view this post on Zulip Joshua Warner (Feb 16 2023 at 22:13):

Should there be a corresponding &1 setter syntax, for tuples?

view this post on Zulip Richard Feldman (Feb 16 2023 at 22:16):

I honestly never thought about it - could be!


Last updated: Jun 16 2026 at 16:19 UTC