I've just been reading through the messages in the past few days - have been largely on the outside of Roc, seeing the occasional talk and blog post over the past couple years. Recently I realised I was really quite interested in seeing the development of this language, especially a functional one with such lofty and unique goals. So this is my part "hello!" message, but also because I'm really not sure if I've quite understood the following, I'm asking here in beginners.
It's my understanding that recently accepted semantics of foo() ? bar
is to - in the case of an Err - map bar on the contained error value before returning early. What I don't understand is how this will affect the below examples:
Kilian Vounckx said:
Richard Feldman said:
yeah I think you'd need parens
In a longer pipe that might become more verbose
With parens:
( foo1() .foo2) .foo3() ? MapErr ) .bar() .baz()
Without parens:
foo1() .foo2() .foo3() ? MapErr .bar() .baz()
With
map_err
and postfix?
:foo1() .foo2() .foo3() .map_err(MapErr)? .bar() .baz()
Is the current consensus that parens will be needed to continue the chain? I.e., for the second option, it's my understanding that SD/methods would lead to (if bar
was a valid function that could be applied to the error value) .bar().baz()
being applied to the error value before returning. Or is this a case handled by whitespace delimiting the ?
(which would be very surprising to me)? Furthermore, I'm really quite fond of being able to use multiple ?
in a chain of method calls, so as to stay on the happy path - would that be facilitated?
I've been (unsuccessfully) trying to catch up and figure out where current opinions and changes lie, so I apologise if I've completely missed the mark here, but I am curious!
Welcome! And that is a really good parsing question. Often for these kinds of syntaxes, you have to imagine what it would look like on a single line to see if the parsing makes sense.
I would assume that this would fail to parse:
foo1()
.foo2()
.foo3() ? MapErr
.bar()
.baz()
Imagine it on one line:
foo1().foo2().foo3() ? MapErr.bar().baz()
Without some form of parens, that really does not make sense.
That said, making it parse (which would require change precedence or whitespace rules) could be really powerful. It would be nice to facilitate something like:
x
.stage1() ? Stage1Err
.stage2() ? Stage2Err
.stage3() ? Stage3Err
But I definitely would not expect that to work by default. It would instead be a follow up syntax that we might enable if there is significant demand and it makes sense for the language.
That is at least my understanding, but someone else who know more about parsing may have corrections.
So in a pipeline, you would have to write it out as:
x
.stage1()
.map_err(Stage1Err)?
.stage2()
.map_err(Stage2Err)?
.stage3()
.map_err(Stage3Err)?
Or I guess add a ton of parens, but that would look really bad
I've been (unsuccessfully) trying to catch up and figure out where current opinions and changes lie, so I apologise if I've completely missed the mark here, but I am curious!
I generally read ~100% of the messaeges that appear on the roc zulip and lately it has been going to fast for me to keep up. As such I have even skimmed or skipped large sections of discussion. So this is super undestandable
@Jonathan checkout
If you would like to see the "future" syntax of Roc.
I see! So the simplified mapping syntax requires the whitespace, and cannot (as it stands) be used within a chain. For that, you would revert to the unsugared map_err
version, with a ?
and no whitespace? I'm just a little fixated on whitespace because it would mean that
x
.stage1()?
.stage2()
(where stage1
returns a Result that we are leaving as-is - no mapping) is semantically different from
x
.stage1() ?
.stage2()
In the latter, could it not be that stage2
is being mapped on any Err returned by stage1
, whilst the former example early-returns the Err from stage1
unaltered, and otherwise unwraps the Ok
and passes the contents to stage2
?
I know this is quite a construed example (if it is even right), and would rely on stage2
being compatible with both Err and Ok values, but I still find it pretty high in terms of surprise or weirdness.
Yeah, I'm not sure how we'll handle that. I would hope that we would just automatically reformat that to:
x
.stage1()?
.stage2()
But I guess it is possible that you really want:
x
.stage1() ? .stage2()
being compatible with both Err and Ok values
Yeah, like if both your Ok and Err value were simply Str.
I guess this is an argument for requiring |x| x.stage2()
instead of allow .stage2()
alone to mean that.
Though I would assume this is a pretty rare mistake in practice (I mean, maybe a common typo, but very rare that it dosen't fail typechecking)
I think a lot of the proposed builder behavior strongly relies on having stuff like .stage2()
, so I'd be really surprised if that did get implemented
(Wouldn't you also have the same issue with shorthand record access functions? (.field
))
I suppose I'm coming at this and seeing PNC proposed (not mainly to be sure, but definitely in no small part) due to its larger scale familiarity, and how Gleam benefited from that, and I'm just wondering if in this case it's worth having two very similar looking ways to do this (.map_err(f)?
and ? f
) that could quite conceivably confuse someone, no less beginners (for instance, me). I suppose the type checker in this case could provide something like
did you mean .stage1()?
^^^ ~~~ (No whitespace between ? and the ...
Yeah, I think the common case won't be long pipelines, but many individual variables that each want to wrap their error.
x = do_foo!(a, b) ? FooErr
y = x.something() ? SomeErr
z = y.map(...) ?? ""
Or if you don't care about error wrapping, just ?
alone
I do agree that it could just be left to .map_err
and that wouldn't be too verbose:
x = do_foo!(a, b).map_err(FooErr)?
y = x.something().map_err(SomeErr)?
z = y.map(...).with_default("")
I do like that the extra spacing makes it easier to separate the ok from the error path though. Kinda like the after thought was wrapping the error.
Anyway, I'm sure we'll see how it pans out in practice and there will be multiple iterations on the error messages.
I think the idea is to encourage wrapping errors with Tags, to give additional context
So this makes it really easy to do that
Jonathan has marked this topic as resolved.
Last updated: Jul 06 2025 at 12:14 UTC