Record projection syntax

What do folks think of a record field projection syntax for PureScript? Something like -

foo = {x:1, y:true, z: Nothing}
bar = foo {.x, .y}
-- bar is now {x:1, y:true}

If we’re talking about the feature in general, I think that could be useful in a few contexts. Otherwise, the fastest way to get what you want is

extractXY { x, y } = { x, y }
bar = extractXY foo

If we’re bikeshedding on syntax, that’s another issue altogether. I’d propose:

foo.{x, y}

because I don’t need to deal with the . character inside the labels, making it easier to copy-paste such records. Moreover, if leading/trailing commas are ever supported, we don’t get IMO some weird code like:

foo {, .x, .y, }

I don’t mind that syntax either, as long as we get the same functionality.

The syntax I proposed follows the existing scheme of eliding the . operator when updating fields -

Updating a single field - foo.x = 1 (if it were possible to update fields in this way in PureScript)
multiple fields - foo {x = 1, y = 2}

I guess it would be nice to have a consistent syntax for update as well - foo.{x = 1, y = 2}, but that would break a lot of code out there.

1 Like

Just as an example of how you might do this in stock PS, because implicit record projections came up in Discord:

https://try.purescript.org/?gist=365396bb83b582cd857e413cc2f963f5

With the optimizer, this can turn into what you would write by hand:

const test = wat => ({baz: wat.baz, foo: wat.foo});
2 Likes

That is amazing, thanks @natefaubion! Perhaps this should go into purescript-record or another well known library.

I’ve been using pick from Records.Extra for this, so you can have:

foo = {x:1, y:true, z: Nothing}
bar :: {x :: Number, y :: Boolean} = pick foo
1 Like

That implementation of pick is a little strange to me. It uses the type system to build a List of string keys, converts it to an Array, and then passes it off to the FFI to build the record. If an intermediate data structure is built for the keys, you might as well go ahead and build a Record.Builder as your intermediate operation. In my example above, I build a Record immutably, but that’s because it’s a much simpler API and the optimizer handles Record and Record.Builder equally well.

1 Like

In our codebase we have a function trim whose type is the same as pick, but implementation is just trim = unsafeCoerce

Sure it’s not completely honest, because the runtime value retains the extra fields, but works well enough in most cases.