Stream: show and tell

Topic: niclas-ahden/roc-maybe


view this post on Zulip Niclas Ahden (Jul 17 2026 at 13:58):

The infamous https://github.com/niclas-ahden/roc-maybe is here :tada: However, before using it, please ask yourself if you really want to? Many of us come from languages with less expressive type systems than Roc, so we may miss out on its awesomeness if we default to old habits. I encourage you to look at Roc's tags with payloads first, with which you can often create way nicer APIs than with a Maybe! You can see examples of that in other packages or platforms like basic-cli. If your heart is still set on a Maybe, here it is :love:

view this post on Zulip Anton (Jul 17 2026 at 14:06):

This can be a useful thing to link to to help people make their decision: https://roc-lang.org/faq#option-type

view this post on Zulip Niclas Ahden (Jul 17 2026 at 14:09):

For sure! Thanks, I’ll add that to the README!

I was debating whether to release this at all, frankly, but yeah :man_shrugging: Let me know if people prefer that I remove it!

view this post on Zulip Anton (Jul 17 2026 at 14:20):

Someone was going to make it eventually :p
It's good that we have the necessary caveats in the README.

view this post on Zulip Niclas Ahden (Jul 17 2026 at 14:29):

Indeed. I think I'd stop using it if I could use common transformations like map, with_default etc. on tag unions in an ergonomic way somehow. I use it mainly for null handling in database requests etc. where there truly is an optional value and there's no more information to convey. I could have a Null type for my db queries to be more domain-specific, but that's just Maybe in different clothing, and I'd have to redefine it for each "domain" :shrug:

view this post on Zulip Richard Feldman (Jul 17 2026 at 14:57):

Niclas Ahden said:

Indeed. I think I'd stop using it if I could use common transformations like map, with_default etc. on tag unions in an ergonomic way somehow.

I've thought about this! We can infer map for structural tag unions that are shaped like Maybe for sure.

view this post on Zulip Richard Feldman (Jul 17 2026 at 14:58):

I wonder if there's a more generic name we could use than "with default" since not all of these would have "maybe" semantics necessarily

view this post on Zulip Kasper Møller Andersen (Jul 17 2026 at 18:31):

Would the inferred functions be present only for types that are defined outside a function, or would they be inferred any time some function takes an argument that happens to be like [MyThing String, OtherThing], even if this type is never used anywhere else?

view this post on Zulip Richard Feldman (Jul 18 2026 at 22:16):

Richard Feldman said:

Niclas Ahden said:

Indeed. I think I'd stop using it if I could use common transformations like map, with_default etc. on tag unions in an ergonomic way somehow.

I've thought about this! We can infer map for structural tag unions that are shaped like Maybe for sure.

ok this PR added it for map - try it out!

view this post on Zulip Richard Feldman (Jul 18 2026 at 22:16):

we can add with_default later after figuring out what the name should be :smile:

view this post on Zulip Richard Feldman (Jul 18 2026 at 22:19):

@Kasper Møller Andersen the way it works is:

view this post on Zulip Jonathan (Jul 18 2026 at 23:57):

Is there any reason to have the derived function just be "map"? I ask because that limits you to only having the shape of a tag union as you describe, and I don't know if it's useful to standardise the name to map, as you can't parameterise over "something that defines map" without higher kinder types, or some associated type derivations. Given that Try has map_ok and map_err, what would be the downside of following suit and auto deriving snake case map_my_tag_name for each single-payload tag instead?

view this post on Zulip Richard Feldman (Jul 18 2026 at 23:58):

what's the use case?

view this post on Zulip Jonathan (Jul 19 2026 at 00:03):

I suppose I'm looking at it more like, if it's going to be added, limiting auto-derived map to just the case where there's one single payload tag seems like an artificial constraint, due to calling it map, which then needs a resolution for "which payload should be mapped?". I think it's fair to say that, if there are cases where you want to use something different but similar to Maybe whilst retaining the conveniences of map et al., you could also want something similar to Try with similar conveniences. But I will try and think of something more concrete :smile:

view this post on Zulip Jonathan (Jul 19 2026 at 00:09):

In the most minimal case it's the same behaviour, just with a suffix on the map. But say you add a tag later on after you realise there is another state, are you now locked out of using map if it's a structural union? (I'm not sure but) I suppose it also doesn't work with open tag unions currently, because you can't guarantee that .. doesn't contain another tag with a payload?

view this post on Zulip Jonathan (Jul 19 2026 at 00:27):

Something more concrete :sweat_smile: Say you were waiting for data in a RemoteData style. NotAsked, Loading, Success(Data), Failure(Error)]. You might want to map_success or map_failure.

view this post on Zulip Richard Feldman (Jul 19 2026 at 04:08):

I'd rather wait until we have an actual use case in practice, like Maybe :smile:

view this post on Zulip Kasper Møller Andersen (Jul 19 2026 at 05:02):

Richard Feldman sagde:

Kasper Møller Andersen the way it works is:

I understood as much at least :smile: my question was really whether this works such that only types that are defined in a module get map functions, or if these are created for any random assortment of tags that fit the bill in a given function?

view this post on Zulip Richard Feldman (Jul 19 2026 at 05:06):

these are created for any random assortment of tags that fit the bill

:point_up: this

view this post on Zulip Kasper Møller Andersen (Jul 19 2026 at 07:21):

I appreciate making it easy to map as needed, but I don’t really like the implications of this. For example, if I publish a package with some Maybe-shaped type, isn’t map now part of my API, maybe even if my type is opaque? And even if it’s not opaque, if I expect to add more tags later, I may only want map_{tagName} in my API, because those are forward compatible.

view this post on Zulip Richard Feldman (Jul 19 2026 at 11:38):

no, this is only for structural tag unions

view this post on Zulip Richard Feldman (Jul 19 2026 at 11:38):

it's just like is_eq - you have to opt into it for nominal types if you want it

view this post on Zulip Richard Feldman (Jul 19 2026 at 11:39):

but now you can say map : _ (or write out the type) to get the automatic implementation if you don't want to implement it yourself

view this post on Zulip Kasper Møller Andersen (Jul 19 2026 at 13:38):

Oh okay, then I’m not worried :smile:

view this post on Zulip Kasper Møller Andersen (Jul 19 2026 at 13:59):

I’m not sure I would do anything for with_default though. From a performance perspective, with_default tends to be a negative influence, because it requires you to fully construct your default value, even if you end up not using it. Using raw pattern matching does not have that concern, because you only construct the default value in case you take the given branch. Of course, if your default value is just an integer, then there is no performance concern, but a default value can be anything.

In Roc, you could do a database lookup to fetch your default value, and without lazy evaluation on that, with_default becomes much more expensive than pattern matching. You can then introduce a with_lazy_default that takes a function to run instead. Or maybe use that as the only option?

Anyway, my point is that I’d rather not make something like with_default too easy, so we can avoid the footgun :grinning_face_with_smiling_eyes:


Last updated: Jul 23 2026 at 13:15 UTC