splitting off from this:
Richard Feldman said:
I think it's reasonable to start new threads to discuss alternative designs to optional record fields.
here's a variation on a proposal Dillon Kearns made for Elm, that would be idea for an alternative to optional record fields in Roc:
suppose I could write this in Roc:
{ firstName: "Sam", lastName: "Sample", .. }
...and this was syntax sugar for this function:
\rec -> { rec & firstName: "Sam", lastName: "Sample" }
so the addition of the .. makes it an updater function
and I could do { .. } to get a function with the type {}a -> {}a
this would facilitate a pattern where instead of asking for a config parameter like this:
range: { start: Num a, stop : Num a, step ? Num a } -> List (Num a)
...I could do this:
range: (Config a -> Config a) -> List (Num a)
where Config a : { start : Num a, stop : Num a, step : Num a }
I like how this looks at the call site, because the .. explicitly shows that there's potentially information being omitted - I like that compared to the status quo
it's also nice to be able to remove a primitive from the language and replace it with syntax sugar, since that simplifies the language
it has a few downsides though, including:
? versionSo take an update function, then apply that to your default record?
Use a nice syntax to make the update function convenient to write
right
One downside of this approach is that it seems to require defining defaults for every field of the record. This doesn't align very well with the range use case, because there is no sensible default for stop.
I don't mind having more language features (as a user of the language, not a compiler implementer), if those features work together nicely withig the language. I always think of go. At first sight the language seemed like it was ducktaped together (Why can't "range" be a userland function? Did you really create so many special cases inside the language, that I could not create myself?). But when I start to use it... It's great. Although it is a separate language feature having a "range" which could have been created utalizing better primitives, it does not make using go harder.
In that spirit, I would stay with the current version compared to the update syntax when discussing default values.
Another important downside: I don't use roc_ls, but I suspect for the current implementation, it is easy to display the default value of a record field when the editor asks for a function signature (hovering over the fn with the mouse for example). With the proposed change, there would be no way to do that. Well, there would, but even writing it down feels silly. We make a very specific rule that sometimes applies, sometimes not like "if the function starts with calling an updater function (defined as only updating record fields) with a record full of default values (compile-time decidable values), treat that record as if it was in the signature...)
Is it explained somewhere why we do not allow & to add fields to records if not present, not just overwrite it? It would solve timotree 's prev comment and would make it easy to have default values for records, albeit not inside the function signature, which has the same problem as I have described before with the language server no being able to display it.
I am sure it isn't so simple as in a dynamic language like js with it's { hostAt: "localhost", name: "default app name", ...myConf, } syntax. Does it make the code more error prone, is it not possible without extra runtime code, would it need complicated compile time transformations? My first instinct was to write this for roc:
createServerConf = \conf -> { conf & hostAt: "localhost", name: "default app name"}
# provide only the fields i want to overwrite
serverConf = createServerConf {name: "Not the default named app"}
# the hostAt field was added to the cerverConf record.
expect serverConf == {hostAt: "localhost", name: "Not the default named app"}
Norbert Hajagos said:
Is it explained somewhere why we do not allow
&to add fields to records if not present, not just overwrite it?
it's possible in theory, but we haven't tried implementing it :big_smile:
Oh, very exciting! If we had that feature, we wouldn't need optional record fields. You could easily have the defaults be an ordinary record and override the fields with a record update from the arguments. But... You would still have to destructure the argument, since recordWithDefaultValues & recordFromArgument is not a valid syntax. You have talked about that already. We are back where I like the current impl better.
I personally am not a fan of this much at all. I think it is more verbose and confusing than default valued fields. Also, I think that requiring defaults for everything totally ruins the feature.
This feels a bit too magical to me
Last updated: Jun 16 2026 at 16:19 UTC