Stream: beginners

Topic: Optional record fields


view this post on Zulip Martin Stewart (Nov 17 2021 at 20:05):

I read about optional record fields here https://github.com/rtfeldman/roc/blob/trunk/roc-for-elm-programmers.md#optional-record-fields but one thing I didn't understand is the motivation behind them. To me it seems like the record update approach Elm does for config works fine without the need for additional syntax.

view this post on Zulip Richard Feldman (Nov 17 2021 at 20:36):

so besides the inconvenience factor, one downside of the defaultConfig alternative is that it means adding new config options has to be a breaking change, even if there's a safe default that could be provided in all cases

view this post on Zulip Richard Feldman (Nov 17 2021 at 20:36):

with optional fields, you can always add new optional fields to your public API and have it be fully backwards-compatible

view this post on Zulip Richard Feldman (Nov 17 2021 at 20:37):

(not just in practice, but also from the perspective of the package manager's semver inference)

view this post on Zulip Lucas Rosa (Nov 17 2021 at 20:37):

Yea exactly it provides some nice ergonomics for using records as configs

view this post on Zulip Martin Stewart (Nov 17 2021 at 20:46):

Good point about avoiding major version changes, I hadn't considered that.

Writing rules in elm-review has made me quite apprehensive about adding conveniences to a language's syntax but I understand the advantages with having optional record fields now. Tradeoffs as always :smile:

view this post on Zulip Richard Feldman (Nov 17 2021 at 22:02):

Writing rules in elm-review has made me quite apprehensive about adding conveniences to a language's syntax

now I'm curious! What rules in particular? :big_smile:

view this post on Zulip Martin Stewart (Nov 17 2021 at 22:48):

I don't know of any rules that would be directly impacted by optional record fields (maybe the NoUnused rules would get significantly more complicated?) But having it means a more complicated AST, which means more situations that a rule might need to handle in order to be useful.

Here's an example that's a bit contrived because in practice it wouldn't be an issue but maybe can give an idea of possible problems:
At work I've created a rule that converts Element.paddingEach { left = 4, right = 4, top = 0, bottom = 0 } into Element.xy 4 0. With optional record fields, maybe elm-ui would be changed so that someone could write Element.paddingEach { left = 4, right = 4 }. Now this rule would need to look up data from the elm-ui package to know what the defaults are. Again, this is contrived because I'm pretty safe in just assuming the default is 0.

More generally though, I don't know of any other language ecosystem that has something like elm-review*. I believe this is because Elm hits a sweet spot of lightweight syntax and pure functions that makes it easy for anyone to write their own rules or tools. For that reason I'm cautious about adding syntax features, it seems like it can quickly move a language out of the "easy to create tools" zone.

*There are static analysis tools and linters for other languages created by one or more experts but I don't know of any tools in other languages that make it easy for just about anyone to quickly create a useful rule.

view this post on Zulip Richard Feldman (Nov 18 2021 at 00:11):

gotcha, thanks! Roc's syntax is very similar to elm's overall, and there aren't any plans to make any significant expansions to it. I want it to stay small too!

view this post on Zulip Lucas Rosa (Nov 18 2021 at 00:39):

(deleted)

view this post on Zulip Martin Stewart (Nov 18 2021 at 08:46):

Oh, I thought of some more questions:

  1. I guess optional record fields can't be equatable? Otherwise what should happen here { fieldA = 5 } == {}?
  2. If they aren't equatable, doesn't that mean it's a breaking change to add the first optional field to an equatable record?
  3. Will compiler error messages get worse? For example here there's some ambiguity
a = { fieldA = 5 }
b = {}
c = a == b # Did the user forget to add fieldA? Or are they trying to test equality on two optional records?

view this post on Zulip Folkert de Vries (Nov 18 2021 at 09:23):

optional fields are not fields with defaults

view this post on Zulip Folkert de Vries (Nov 18 2021 at 09:24):

so { fieldA : 5 } == {} is just a type error

view this post on Zulip Folkert de Vries (Nov 18 2021 at 09:26):

rather you can check if the field is present in the type, and if not you could then provide the default. It's not baked in

view this post on Zulip Folkert de Vries (Nov 18 2021 at 09:30):

it's a bit like polymorphism. In elm you cannot say

x : { a | y: Int }
x = { y = 5 }

view this post on Zulip Folkert de Vries (Nov 18 2021 at 09:33):

you also cannot do this

f : { a | x: Int } -> Bool
f = \r -> r == {x= 5, y= "foo"}

view this post on Zulip Martin Stewart (Nov 18 2021 at 10:02):

Okay, I think I understand. Writing f : { a : Int, b ? Int} -> Int is like writing this?

f : { a : Int }* -> Int
f = \record -> fHelper record |> ... #rest of the function

fHelper : { a : Int }e -> { a : Int, b : Int }e

view this post on Zulip Folkert de Vries (Nov 18 2021 at 10:07):

I think you're on the right track. Both of these would fail the type checker

f : { a : Int, b ? Int} -> Int
f = \r -> { a : 42, b : 43 }

f : { a : Int, b ? Int} -> Int
f = \r -> { a : 42 }

But you can do

f : { a : Int, b ? Int}, { a : Int, b ? Int} -> Int
f = \r1, r2 -> r1 == r2

view this post on Zulip Folkert de Vries (Nov 18 2021 at 10:08):

or, at least I think you should be able to do that. Not sure if that actually works today now that I look at it

view this post on Zulip Folkert de Vries (Nov 18 2021 at 10:09):

or rather, I don't think the error message is great if you do f { a } { a, b }

view this post on Zulip Ivo Balbaert (Jul 09 2023 at 17:36):

Maybe I'm doing something silly, but I can't get a working example out of the tutorial section: Optional Record Fields. All variations on the following code:

main =
   # dbg table { height: 100, width: 150, title: "a", description: "b" }
   # dbg table {100, 150, "a", "b"}
   # Stdout.line "\(table {100, 150})"
   Stdout.line "the end"

table : { height : Num, width : Num, title ? Str, description ? Str } -> Str
table = \{
        height,
        width,
        title? "oak",
        description? "a wooden table"
    }
    -> "abc"

give the following compiler error (compiling hangs):
thread '<unnamed>' panicked at 'internal error: entered unreachable code: Any other pattern should have given a parse error', crates/compiler/can/src/pattern.rs:737:26
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Here is the stack backtrace:
stack backtrace:
   0: rust_begin_unwind
             at /rustc/90743e7298aca107ddaa0c202a4d3604e29bfeb6/library/std/src/panicking.rs:575:5
   1: core::panicking::panic_fmt
             at /rustc/90743e7298aca107ddaa0c202a4d3604e29bfeb6/library/core/src/panicking.rs:65:14
   2: roc_can::pattern::canonicalize_pattern
   3: roc_can::scope::Scope::inner_scope
   4: roc_can::def::canonicalize_pending_body
   5: roc_can::def::canonicalize_value_defs
   6: roc_can::module::canonicalize_module_defs
   7: roc_load_internal::file::run_task
   8: core::ops::function::FnOnce::call_once{{vtable.shim}}

Perhaps this is just a design example and isn't supposed to work. But if possible I would like to have a more expert advice on this :smile:

view this post on Zulip Luke Boswell (Jul 09 2023 at 18:45):

Looks like a bug, can you log an issue for this?

view this post on Zulip Ivo Balbaert (Jul 09 2023 at 19:58):

Ok: # 5653

view this post on Zulip Kilian Vounckx (Jul 10 2023 at 07:26):

@Ivo Balbaert I don't know if it's exactly the same, because I don't have my laptop on me. I had a similar error. Putting all function arguments on one long line worked for me

view this post on Zulip Kilian Vounckx (Jul 10 2023 at 07:28):

table : { height : Num, width : Num, title ? Str, description ? Str } -> Str
table = \{ height, width, title? "oak", description? "a wooden table" } ->
   "abc"

Like so. Not the prettiest, but it should work until the bug is fixed. At least if it was the same as mine

view this post on Zulip Ivo Balbaert (Jul 10 2023 at 09:17):

Thanks Kilian, that works! I'll mention your workaround in the issue, but leave it open until the parsing problem is fixed.


Last updated: Jul 06 2025 at 12:14 UTC