Stream: API design

Topic: Minor annoyance when default field pattern shadows function


view this post on Zulip Jasper Woudenberg (Nov 01 2024 at 21:04):

I ran into a weird edge of the optional field design and wondered if we're aware of it. It's probably unimportant, but I thought I'd share.

The context here is that I'm working on a module that exposes an API for construction an HTML element. There's some names that are used both by HTML tag names and attributes. Because of this, I was trying to write something like this simplified example:

title = \{ id ? "", class ? "", title ? "" }, children -> node "title" { id, class, title } children

Roc doesn't accept this because the title name in the pattern shadows the title of the top-level function. There's no great options for improving this though. Removing the pattern matching means you cannot make use of default field names, and renaming either attribute or tag name because of implementation requirements would be a shame.

I can workaround it by using two modules, so this is definitely a papercut and nothing blocking, but papercuts are not delightful so I thought I'd share anyway :).


view this post on Zulip Richard Feldman (Nov 01 2024 at 22:03):

hm yeah as would fix this

view this post on Zulip Richard Feldman (Nov 01 2024 at 22:04):

something like

title ? "" as t

view this post on Zulip Jasper Woudenberg (Nov 01 2024 at 22:07):

Yeah. I briefly tried this thinking it was already in the language, but it's just for .. patterns, right?

view this post on Zulip Brendan Hansknecht (Nov 01 2024 at 22:14):

It used to be for more things. It existed before ... but I think it is pretty limited currently

view this post on Zulip Jasper Woudenberg (Nov 02 2024 at 14:25):

One more thing I ran into, that I think is less edge-case-y than my earlier example: I don't think there's a way to make nested default fields work. If there is I couldn't get it to work.

Suppose you have the following type:

attributes : { aria ? { disabled ? Str } } -> List U8

You can define that just fine. You can also construct values to pass to this function. But I've not found a way to get my hands on the disabled value in the argument, if it exists.

attributes = \{ aria ? { disabled ? "" } } -> ...

This is not allowed because the default value is not a pattern and so can't contain ? .. defaults.

attributes = \{ aria ? {} } -> ...
attributes = \{ aria ? { disabled: "" } } -> ...

Both are not allowed, Roc will complain about a type mismatch for the inner record between what's requested ({ disabled ? Str }) and what's provided ({} or { disabled : Str }).

attributes = \attrs ->
    { disabled ? "" } = attrs.aria
    ...

Not allowed because we cannot use attrs.aria given .aria is an optional field.


Last updated: Jul 06 2025 at 12:14 UTC