The record shorthand is quite handy: {a: 42} |> .a is much nicer than {a: 42} |> \rec -> rec.a.
To access fields in nested records, we can chain pipes and record shorthands:
{a: {b: {c: 42}}} |> .a |> .b |> .c
Perhaps this has been discussed before, but it would be nice to be able to write this instead:
{a: {b: {c: 42}}} |> .a.b.c
In particular, it would make List.map a bit nicer when dealing with nested records. For example:
[{a:{b:1}}, {a:{b:2}}, {a:{b:3}}] |> List.map \rec -> rec.a |> .b
Would become:
[{a:{b:1}}, {a:{b:2}}, {a:{b:3}}] |> List.map .a.b
just to be fair, I think today, users would write:
[{a:{b:1}}, {a:{b:2}}, {a:{b:3}}] |> List.map \rec -> rec.a.b
But yeah, I think nested record update and record selection would be great
Oh true, I hadn't thought of that. Just a nice-to-have.
Yeah, unless we want to discourage nesting records, I think this should just work
yeah Elm's approach is to intentionally not have nested record updates, but I think Roc should have it
briefly, my view on it in Elm was:
I also think in terms of overall design philosophy in Roc, I'm okay with having tools that people reach for a lot when they're starting out, and then reach for less and less over time, as long as there's a reasonable path to get from the one to the other
which in the case of nested record updates, I think there is
less so if someone falls into the lens trap, but hopefully nested record updates can reduce the chances of a beginner doing that
Having spent a lot of time over the last two years messing with loads of Kubernetes resources as jsonnet objects, I can certainly attest to the value of easy access to nested record fields; jsonnet is generally great for messing with objects/records, but things quickly got verbose.
Richard Feldman said:
less so if someone falls into the lens trap, but hopefully nested record updates can reduce the chances of a beginner doing that
Pardon my ignorance, but what's the lens trap?
Lenses are a DSL for defining paths into (deeply nested) datastructures, that can then be used to read or mutate whatever part of a type they point to.
So imagine you could define a path like .a[2].b (the lens), and then use that in combination with a setter function, or a getter function, or an update function, on some value of a compatible type.
I'm not entirely doing the idea full justice here, there's much wilder applications of them.
I know it primarily from Haskell, where there's a couple of libraries that offer lenses, then other libraries that use it for their APIs. For instance, the most populat AWS-wrapper library in Haskell (amazonka) uses lenses heavily, last I checked.
Here's a post with some more details if you're interested:
https://www.haskellforall.com/2013/05/program-imperatively-using-haskell.html
Aurélien Geron said:
Richard Feldman said:
less so if someone falls into the lens trap, but hopefully nested record updates can reduce the chances of a beginner doing that
Pardon my ignorance, but what's the lens trap?
the lens trap is where you use lenses
but to be less glib about it, to me lenses are basically a way of taking code that isn't structured well, making the code more complicated on top of that, and then as a bonus making it harder to restructure the code to be better in the future
Try searching for “lens…” on https://package.elm-lang.org :wink:
Thanks! I'm quite new to FP, I just hadn't heard the term "lenses" before. So IIUC a lens is a tool meant to simplify accessing part of a complex data structure, but in practice they often end up making the code worse and you're better off trying to simplify the data structure or making it an opaque type with a clean interface.
that's essentially what I would say personally, but you can definitely find people who think lenses are great :big_smile:
the reason I call it a trap is that the way people who like lenses present them as like a "powerful abstraction" or something like that
No one ever said Roc wasn't opinionated
It's a good thing
so they can sound really enticing and cool, but also kind of tricky because there's new terminology and such, but then you start using them and pretty soon your code base is in trouble and now you're stuck
python -m this | grep Flat
Lenses make me think of OOP, where everything gets its own abstraction, and your code ends up 50 layers deep just to print Hello World.
Lenses and prisms are better be avoided. They are typical premature abstractions - that lead to dorky grins and beard stroking.
That's the reason why I’m wary of fun and excitement in coding. Most of the exciting and fun features/syntax carry inherent complexity. Every time I hear “it's so fun to use x”, I’m thinking it should be avoided. I like to say that the actual feature of Elm is that it’s boring.
E.g. I think backpassing is fun to use because it allows me to see callbacks in a different perspective. Red flag!
Ofc I’m exaggerating. But it helps me to solve “I want to use” vs “I need to use”
For myself personally, I don't care about the nested access case. I think it is fine. Using the name is fine. I rarely ever use the .field access syntax anyway.
On the other hand, nest record updates are a pain. If you want to update the inner field of a record, it is a weird double update syntax that is very verbose and less intuitive.
Separately, I don't think that syntax optimizes properly
IMHO: one of the most nasty and terrible experience of Elm was the intentionally crippled record handling and all I ever got from the community was that using nested records is a bad design. What?
For the goodness sake, I could never understand what's wrong and how to fix the "nested record" issue. Like having a record with few separated things, like { personalData, shippingData, billingData } in a record was something "bad", because each entity has it's own structure and is sometimes even optional. And what if :scared: each of these entities had e.g. an address field? This must be a disaster.
Now it seems like it all comes back again. The darkest darkness returns :scream: Can someone, please, explain it to me, or at least point into some papers about the issue and paths to resolve, so I could finally understand?
in Roc we just haven't implemented it yet
I have implemented nested record access as a closure shortcut in my fork, so this:
[{a:{b:1}}, {a:{b:2}}, {a:{b:3}}] |> List.map \rec -> rec.a.b
becomes this:
[{a:{b:1}}, {a:{b:2}}, {a:{b:3}}] |> List.map \.a.b
And to make it more fun, this code is for identity function :)
[{a:{b:1}}, {a:{b:2}}, {a:{b:3}}] |> List.map \.
Cool, are you exploring different designs?
Luke Boswell said:
Cool, are you exploring different designs?
Hi, I was interested in the Roc and started digging into the codebase, because I am learning Rust as well.
Have started with parser. To be interesting, set a goal adding a feature. Something small, around a single idea. And likely went overboard :-)
I've added "closure shortcut" for a single parameter closure/lambda (not sure about the official Roc wording).
Currently, it supports the binary operators, including |>, and record, tuple access, and identity function,
and... a binary operator for when as ~ which can be combined with closure shortcut.
Examples:
List.map [1, 2, 3] \+1
List.map [{ foo: { bar: 42 } }] \.foo.bar + 5 // the lambda is \x -> x.foo.bar + 5
pipe is also a binary in Roc:
List.map [1, 2, 3] \|> Num.add 9
Binary operator for the pattern matching similar to the piping:
List.map [1, 2, 3] \x -> x ~
1 -> 42
_ -> 13
and as closure shortcut
List.map [1, 2, 3] \~
1 -> 42
_ -> 13
There is also a thing you can do to expand the closure if needed. You put space after slash and 'roc format' will expand the code into the full closure:
List.map [1, 2, 3] \ * 2 + 1 // check the space after \
on format expands into
List.map [1, 2, 3] \x -> x * 2 + 1
This is cool to see! I also find that just diving into codebases and trying stuff is one of the best ways to learn how they work, including learning the language they're written in.
This feature looks pretty cool as well. Just to set expectations on your end: we try to have Zulip discussions (usually in the #ideas channel) about new features before we consider merging them. So if you're just messing around to learn about Roc and Rust and compilers, awesome! But if you mean to add this "closure shortcut" to Roc, please consider creating a discussion topic so we can get alignment on how it'll look.
I think you should also take a look at the new lambda syntax to see if it changes your experiments in any way!
@Georges Boris Yep, it is ruining my experiments. Language moves too much at the moment, so the Parser experiments would fail by default.
The parsing will be changing a good bit for the next 3 to 6 months, it feels like
Last updated: Jun 16 2026 at 16:19 UTC