Stream: ideas

Topic: Requiring `{ ..{} }` for closed record captures


view this post on Zulip Sam Mohr (Aug 24 2024 at 16:42):

Right now in Roc, a { a, b } = capture will work for destructuring any record that has at least an a and a b field. This is concise, but is not exhaustive, so it will not lead to a type error if the destructured record adds a new field to its type.

As part of the new .. syntax proposal, we are writing open record type definitions as User a : { name : Str, age : U64, ..a }, meaning User a is an open record with extension type a. If we wanted to create a closed record here, we could substitute a with {}, so User {} : { name : Str, age : U64, ..{} }.

What do people think of requiring that ..{} at the end of record captures, meaning { a, b } = would always need to be written { a, b, ..{} } = if we wanted to only allow capturing if only the fields a and b (and no others) were present? We'd probably support { a, b, .. } = or { a, b, ..rest } = to allow ignoring extra fields.

Would you like this change? Would you hate it? Comments below!

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 16:57):

I think it should be an optional opt in

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 16:58):

{a, b} = ... works with any record containing a and b.

{a, b, ..{}} = works with records that only contain a and b

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 16:58):

That said, not sold on the syntax

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 16:59):

But I like the idea of being able to specify that the record pattern match is exhaustive

view this post on Zulip Jasper Woudenberg (Aug 24 2024 at 17:15):

I like the proposed syntax. I can't know for sure, but I think if I encountered the new syntax without having a familiarity with Elm or Roc I might be able to grasp what the syntax means just based on seeing it used, which I don't think is true based on the current syntax.

view this post on Zulip Sam Mohr (Aug 24 2024 at 17:23):

Even though it's a breaking change, I'd prefer { a, b } to capture a closed record, and { a, b, .. } for open records

view this post on Zulip Anton (Aug 24 2024 at 17:42):

Yeah, that would be my preference as well

view this post on Zulip Richard Feldman (Aug 24 2024 at 17:45):

I'm open to try that

view this post on Zulip Richard Feldman (Aug 24 2024 at 17:45):

it certainly feels the most straightforward and most likely to catch errors

view this post on Zulip Sam Mohr (Aug 24 2024 at 17:56):

I'm personally strongly opposed to ..{} for aesthetics

view this post on Zulip Sam Mohr (Aug 24 2024 at 18:02):

But if it's the right syntax, I'll grin and bear it

view this post on Zulip Anton (Aug 24 2024 at 18:07):

But if it's the right syntax, I'll grin and bear it

To clear up the confusion; Richard is in favor of "I'd prefer { a, b } to capture a closed record, and { a, b, .. }" not for "..{}", right?

view this post on Zulip Sam Mohr (Aug 24 2024 at 18:26):

I agree, but if we change our minds, I don't wanna block progress

view this post on Zulip Richard Feldman (Aug 24 2024 at 18:29):

yeah I think we should try it the way Rust does it, e.g. make a breaking change :+1:

view this post on Zulip Sam Mohr (Aug 24 2024 at 18:33):

Great! I like type errors helping me facilitate refactors, which this helps with

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 22:17):

Yeah, I'm for it too. A little inconvenient in function inputs, but mostly good. Also, we will require .. for open record too?

As in, would this be valid:

someFn : { a: Str b: Str, .. } -> Str
someFn = \{ a, b } ->
    ....

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 22:17):

I think this is important to think about related to defaults when users don't add types

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 22:18):

Can I pass { a, b, c } to:

someFn = \{ a, b } ->
    ....

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 22:19):

Probably not, but I do think that will be the largest ramification if we require ... I think it is a very common pattern to only use a subset of record fields from a function input. Or to accept any record that happens to contain the fields you care about

view this post on Zulip Agus Zubiaga (Aug 24 2024 at 22:20):

Yeah, I think .. should be required to make it open even without annotations

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 22:21):

Just want to make sure we consider this. I definitely have tons of functions in this form that I have written over the years.

Kinda makes me think about how tags are open by default in function output. Records being open by default for function input might be really useful

view this post on Zulip Agus Zubiaga (Aug 24 2024 at 22:22):

but tags are open by default even if you have an annotation

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 22:23):

Sure, and records could be that way too...idk, just a general idea.

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 22:23):

But probably a lot less useful

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 22:24):

I can probably manage to add the 4 extra characters to like all functions on a data structure like Dict. , ..

view this post on Zulip Sam Mohr (Aug 24 2024 at 22:25):

It's good to make the default the "right" choice, and open records are a better default, probably...

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 22:26):

Yeah, I think they might be the better default. At least for function inputs. Not sure about other locations.

view this post on Zulip Sam Mohr (Aug 24 2024 at 22:26):

Is there a way we could imply records are closed without taking the current { a, b } syntax away?

view this post on Zulip Sam Mohr (Aug 24 2024 at 22:26):

Besides { a, b, ..{} }, which I'm not a huge fan of

view this post on Zulip Agus Zubiaga (Aug 24 2024 at 22:27):

Flow has {| a, b |}, but I don't like it either

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 22:27):

{ a, b, :door: }. Closed door...the perfect solution.....

view this post on Zulip Agus Zubiaga (Aug 24 2024 at 22:30):

I don't think I would like records in function annotations to be open by default since extra fields wouldn't produce type errors

view this post on Zulip Agus Zubiaga (Aug 24 2024 at 22:32):

If I remove a field from the record annotation, I want the compiler to tell me about all the calls I should remove it from

view this post on Zulip Agus Zubiaga (Aug 24 2024 at 22:34):

Usually,records defined in a function annotation (as opposed to in a type alias) are used as "named" arguments. Think about the positional case, wouldn't you want to get a type error if your function call is passing too many arguments?

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 23:57):

I view it quite differently. I mostly have records that already are defined and are essentially their own types. In any given function that interacts with a type I only use a subset of the fields. So open by default makes a lot of sense. My default is extracting a subset of the fields

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 23:57):

Also, it is no harm generally speaking if an extra field exists. Just a minor missed cleanup.

view this post on Zulip Brendan Hansknecht (Aug 24 2024 at 23:57):

Roc could give a warning that a field is never used for that.

view this post on Zulip Richard Feldman (Aug 25 2024 at 00:26):

I remember talking about this idea with @Ayaz Hafiz at one point. I think it was in the context of tag unions becoming open by default in the output position. So basically, just to summarize what it means, it would mean that a function like this...

foo : { a : Str, b : Str } -> { a : Str, b : Str }

...is guaranteed to return a record with exactly those two fields, but it accepts records with more fields than that

view this post on Zulip Richard Feldman (Aug 25 2024 at 00:26):

and if I remember the conversation right, it seemed like it wouldn't be that useful in practice (e.g. how often do people opt into this in current Roc?) and might lead bugs to be missed somehow (although to be fair I'm not sure exactly what that would look like in practice)

view this post on Zulip Fritz Psiorz (Aug 25 2024 at 11:48):

I'm not a fan of ..{} because it's too subtle. It looks like you can just use { a, b, ..{c, d} }, or {a, b, ..rest }, which is likely not going to be added. It also looks like it should be a no-op just like ..[] for lists.

There's also the question whether { ..{}, a, b } would mean the same thing.


Last updated: Jun 16 2026 at 16:19 UTC