Stream: contributing

Topic: JSON encoding: unit and handling optionality


view this post on Zulip Richard Feldman (Jan 30 2026 at 15:31):

I think this is a complicated issue and I don't think Maybe/Option is the obvious answer. I think the downsides are subtle and nonobvious.

view this post on Zulip Richard Feldman (Jan 30 2026 at 15:36):

for example, a potential design we could consider is having encoding and decoding have a special concept of "optional" tag unions, which are defined as tag unions with exactly 2 tags, where one of them has a payload of exactly one value and the other has no payload (so, the same shape as Option/Maybe/etc.)

view this post on Zulip Richard Feldman (Jan 30 2026 at 15:37):

at that point, the json library doesn't need to be coupled to an Option or a Maybe type, and you can use more descriptive things in your data model than Some/None, e.g. Found/NotFound, Provided/Omitted, Present/Absent, Available/Unavailable, etc.

view this post on Zulip Eli Dowling (Jan 30 2026 at 17:11):

I don't love that idea.
I actually agree that being more specific is good. However there are a few big concerns i have with that.

  1. Compiler only: This is a feature not available to library authors, it's a special case inside the compiler that people have to learn, it feels like a hack.
  2. Option types aren't just about encode/decode: mapping the Some variant, converting the none to a result with a specific error. all of this presumably would need more special compiler magic or not be available.
  3. Unintuitive behaviour: What if i really did just want a tag union with that shape and now it's suddenly getting encoded in an unexpected way? That's weird and annoying and i don't see a solution when that's happening, without introducing some additional language construct or type system feature.

Another option would be to just use a result right?
I do have some objections to that too

  1. optionality is a real world property that needs to be modeled. A thing can exist or not exist. This is common, and very different to failure. Something not existing is not necessarily an error state and calling it one feels confused.
  2. Options can now combine with errors, which again feels wrong, like if I do a http request to check if the fridge has milk. No milk, is not really at all related to 500. One is data representing the world, the other is a software fault.

Just so we're clear I'm fully onboard with trying to find a better alternative and exploring solutions, but obviously thaey have to be better than the status quo, and I think being harshly critical is a good way to figure out if an idea is worth implementing.

I've also considered implementing option in terms of result, just with type aliases None:Err(None) and Some(a):Ok(a). I feel like that keeps error accumulation (if that's wanted) without having the weirdness of none being a failure. I don't like that much either. but it's a thought.

view this post on Zulip Eli Dowling (Jan 30 2026 at 17:11):

Should this maybe be moved to an ideas discussion? I don't have permissions to do that.

view this post on Zulip Richard Feldman (Jan 30 2026 at 19:06):

yeah those are all good points!

view this post on Zulip Richard Feldman (Jan 30 2026 at 19:10):

I think not having Option or similar in the stdlib has been great for return values, because it's so clear that all "this operation failed" APIs should standardize on one type and one way to do it

view this post on Zulip Richard Feldman (Jan 30 2026 at 19:17):

the main reasons I'm trying to find alternatives to Option for these use cases (which I certainly agree are important!) are:

view this post on Zulip Richard Feldman (Jan 30 2026 at 19:18):

but I think if we pull the trigger on adding Option to the stdlib, it will become entrenched and potentially better designs will never get explored properly.

view this post on Zulip Richard Feldman (Jan 30 2026 at 19:21):

one concrete design question: supposing we added support for a concept of "optional record fields" (not just default, but actually optional where if you omit the field when passing the record to a function, the function can actually tell at runtime whether the field was present or missing

view this post on Zulip Richard Feldman (Jan 30 2026 at 19:23):

if we had that, would we still want Option? If so, for what? e.g. JSON happens to make a distinction between "field was present but null vs field was missing" but does that matter from a decoding perspective? And does it matter from an encoding perspective if you can just tell the encode function "use null when I omit a field"?

view this post on Zulip Notification Bot (Jan 30 2026 at 20:16):

19 messages were moved here from #contributing > Worklog (Draft PRs and coordination) by Jared Ramirez.

view this post on Zulip Eli Dowling (Jan 30 2026 at 23:39):

I definitely agree with you there! And I think in the short term a non-stdlib option type, in a package is the way to go. I think that makes it clear that its "a solution" not "the solution" and that things can change.

When writing the json decoder I would include a specific "json option" tag union with [null, undefined, value(a)]
And then also include an option to choose whether normal option becomes null (this should be the default), or undefined.

I think there are rare cases where you want granular control and we should provide it to you.

In fact maybe we should include an "encoding option" because many formats have a differnce between null and missing.
Yaml
Xml had XSI:null vs no tag

We could say "this will make a best effort to encode as whatever null is in your output langauge, but for some it may be the same as undefined"

As for optional record fields I think it would be great if there was a way to have optional fields that supported structural and nominal types. Restricting it to nominal types would make most deserialization and serialization require nominal types which I think would be a shame. I think if would reduce the use of structural types significantly. If they aren't at the edge of your code they likely just won't be used as much inside.

view this post on Zulip Richard Feldman (Feb 02 2026 at 20:02):

we had defaultable fields in structural records in the original Rust compiler, and my conclusion based on how it went is that it wasn't a good idea after all

view this post on Zulip Dan G Knutson (Feb 02 2026 at 20:24):

does current roc have a 'spread' operator that would let you do a rust-style:

thing = Thing {
  color: Red,
  ..Thing.default(),
}

view this post on Zulip Luke Boswell (Feb 02 2026 at 20:30):

https://github.com/roc-lang/roc/issues/7097

view this post on Zulip Luke Boswell (Feb 02 2026 at 20:31):

yes that is the plan I believe

updatedRecord = { foo: 123, bar: "abc", ..record }

Last updated: Feb 20 2026 at 12:27 UTC