There's a good deal of Rust that is designed to flatten out pipelines, and I agree that I'm almost always happier with "flatter" code, a.k.a. code that allows more assignment of variables in preference
One such feature is let-else, e.g.
let Some(value) = maybe_value else {
return "no-answer";
};
Some(value + 123 * 456)
It is a way to add if-style guards instead of
maybe_value
.map(|value| value + 123 * 456)
.unwrap_or("no-answer")
It means that the reader understands the intermediate steps in the program
I’d love some sort match or else assignment now that we have early returns
I think we could consider extending the if-return feature to allow that!
https://github.com/roc-lang/roc/issues/7105
Something like
useResult = \res ->
Ok val = res else
return Err "whoops"
val.callFunc().nowThatItsValid()
8 messages were moved here from #ideas > Tags and dot syntax by Sam Mohr.
(deleted)
I think you'd allow the following syntax:
val = 123, Ok 123 = Ok 123)Made the example less confusing
Deleted my comment that became confusing :wink:
Thanks
We have become braces-less Rust(or Gleam)
I'm okay with this
The look of gleam with performance close-ish to Rust
Not bad
and with platforms
I just wonder if we will now hit a wall with white space sensitivity over braces
Some people HATE WSS
It isn't an issue for languages like python, so should be fine
Good point
I love the concept and principles of ROC so the makeup doesn’t bother me too much
As long as it’s easy to learn and consistent
I'm not afraid to admit I like a good coat of paint on my Ferrari
Breaking the functional contract or the strong locality would be a deal breaker for me
I'm glad it doesn't bother you, then I can get my opinion in first!
Locality is very important! Functional is a no brainer
Locality is huge. Big huge
My least favorite thing in a language is non-locality in imports
In imports?
Yes
Sorry, can you explain what you mean by that?
Like open in F#/OCaml
Oh yeah, gross
Or #include in C/*++
(sorry Ocaml fans!)
Even Elixir has this issue with use
It’s actually a concern I have with static dispatch
When there aren’t type annotations
It’s actually the biggest one
I’m all for parents-and-commas
I'm thinking there may be value in automatically giving type annotations to all top-level defs
I think someone is working on that
Via formatter
Well yes, they're adding a tool to do it, but I want to mandate it
I was gonna do it, but someone started a branch without assigning themselves
Because without top-level defs having written type annotations, static dispatch could get really confusing
Ah, so you can write with none, but to build you have to format it first and get the annotations?
And I’m assuming can run/test without?
Something like that
I’d like to see no-static dispatch Roc with parens and commas
It would basically be the language I made, but no function binding sugar and better lambda syntax
I'm not sure how to deal with the formatter auto-type-annotating defs while you're writing them
Hey, if you're looking for work to do, I don't think anyone is currently working on the parens issue
If there even is a GitHub issue
I haven’t seen it
I have one bug I was gonna look at as soon as I finish my last day of AOC
Which is today :joy:
Rest in peace, advent of code
Christmas will never come :salute:
Too much time for someone with a job and a family and who likes to sleep
And contribute to Roc of course
Though I quite enjoy writing Roc over Rust
Your family will understand that you have higher priorities
But I wonder what @Richard Feldman would think about doing static dispatch in two stages:
Phase 1 lands us at a place syntactically that is close to Gleam with WSS over braces. And if we time it with var and for could be an interesting point to decide if we want to try static dispatch for real (and lower the effort to do so)
We talked about initially supporting both parens and spaces, we'd want to allow both, though I'm not sure how that would work
I think doing it in two stages makes sense
they can coexist because in practice nobody writes foo(bar, baz) today
(it could parse as foo (bar, baz) but nobody would miss it if it didn't)
Sam Mohr said:
There's a good deal of Rust that is designed to flatten out pipelines, and I agree that I'm almost always happier with "flatter" code, a.k.a. code that allows more assignment of variables in preference
One such feature islet-else, e.g.let Some(value) = maybe_value else { return "no-answer"; }; Some(value + 123 * 456)
I'm open to maybe doing some form of this, but I actually dislike the way Rust does it
I think the thing that bothers me about it is that you find out it's a conditional after the fact
like "doot doot doo going along assigning a thing to a WHOA else!!!"
it might be too weird, but one idea could be:
if Ok foo = bar then
# use foo
else
return 42
just allow single = inside an if conditional
I guess then you're still indenting one level, which is fewer than 2, but still more than 0
(if I got my math right)
That's the equivalent of if-let, but it still doesn't do the important part IMO, which is say "let's focus on the important case of when bar is Ok"
Too bad try is already taken
UNLESS
that's an interesting one!
We deprecate try when ? is ready and repurpose it
try Ok x = val else
return "nope"
try Ok foo = bar else
return 42
# use foo
Or you pull from the Ruby book
unless Ok foo = bar
return 42
# use foo
of note, this one doesn't need any different syntax, because of ?:
Sam Mohr said:
Something like
useResult = \res -> Ok val = res else return Err "whoops" val.callFunc().nowThatItsValid()
it can be:
useResult = \res ->
val = res.mapErr("whoops")?
val.callFunc().nowThatItsValid()
which I just realized has the same property of "the conditional part comes at the end" but for some reason it doesn't bother me when it's ?, unlike else :thinking:
I don't really know why that's my reaction to it! :big_smile:
Familiarity bias, perhaps
I think your principle of "earlier info is better info" is great, but ? is an written language thing
So we know it needs to follow different rules
that makes a lot of sense!
I think another thing that bugs me about the Rust style (which doesn't come up with ? either) is when you have like:
if let Ok(foo) = bar {
// ...
} else {
// ...
}
I think what bugs me about it is that it's semantically equivalent to using match with 2 branches, one of which is _ =>, except that it's more concise
which in turn means that it rewards using _ => instead of exhaustive pattern matches
and I dislike feeling torn between wanting conciseness and wanting exhaustive pattern matching
I would rather it didn't offer me the conciseness option so I can always choose exhaustive pattern matching without having to debate tradeoffs in my head
I agree that you should always prefer proper matches over if-let-else
I respect Gleam's lack of if-else for this property
I think we should do unless-return or try-else-return, but not if-let given that
speaking of punctuation, could do something like this:
Ok foo = bar ??
return 42
# use foo
so it's kind of like with_default except lazily evaluated
Are we still planning on doing ??, then?
I don't think we have a concrete plan right now, but we certainly could
actually I guess if it's with_default then it would just be:
val = result ??
return 42
# use foo
or even one line:
val = result ?? return 42
I think it made sense with
try readFile! "path" ? FileReadErr
?? []
Since we were trying to encourage good error handling with terseness
I actually would love to keep these operators
The one I think is more important is the map_err one
Because people don't usually actually map_err in Rust, they just slap anyhow on everything, meaning they don't have a "call stack" of context
yeah, I definitely prefer map_err over anyhow :thumbs_up:
But since ? is staying as it normally works, maybe we can find a different operator for map_err
I'm not sure how well an operator would work in method style though
Well, the problem with Roc as it is today is that we have anyhow
I guess really it's that an operator wouldn't work well with the ? suffix
e.g. foo.bar().map_err(Baz)? works fine
but (foo.bar() ?? Baz)? is awkward
tryThreeThings! = \{} ->
try validateFile! "first.txt"
try validateFile! "second.txt"
try validateFile! "third.txt"
Ok {}
(well it probably wouldn't be ?? I guess, but the point is that an infix operator in the middle of a chain is weird)
Which one failed?
You're right that map_err is the solution here
But it seems like not many people do that, so long as it type checks
yeah I'm not sure if that's fixable though
Though I don't have any evidence that a one-char operator would improve it... yep
any solution is going to be strictly more work than zero work
I can only hope that anonymous error unions make it easy enough
map_err should be fine, though we probably also want map_err!
I think without a map_err operator, we don't want ?? either for the reason you suggested
well with_default comes up pretty often as the last thing you do
as I recall several mainstream languages have exactly ?? as that operator
except they use it for null instead of Result
I'm okay with it, but we've decided for every other pipeline-adjacent operator to use words instead
pass_to instead of |> or .>
map_err instead of ?
yeah, but an operator here seems like it has less downside because with_default is most common at the end of a series of chained calls, plus it makes it work much better with ?? return foo
seems a little "uneven", but we have operators elsewhere
because if it's a method, then it can't be lazily evaluated
.with_default(return foo) doesn't work :big_smile:
?? return Foo only works for results, right?
whereas ?? can work like && and || are intended to in the future
I think that's a little limiting
yeah it would be like result ?? return Foo
hm, how so?
UserStatus : [
LoggedIn(User),
Inactive,
Deleted,
]
checkUserActivity : UserStatus -> Result _ _
checkUserActivity = \userStatus ->
unless LoggedIn user = userStatus
return Err NotLoggedIn
checkStatus(user)
Though you're right that we want short-circuiting for defaults!
So I like that ?? is short-circuiting like && and || will be
If you tell users that they should be converting everything to Results, then we get less-documented code
ah I see what you mean, yeah it doesn't help in the situation where you want to early-return based on pattern matching on a non-Result :thumbs_up:
Like in our canonicalization code where we use Result<Value, Type> because it's a convenient version of Either
right haha
Instead of doing Value | Type
might be confusing, but we could have ?? work differently when used after a single-pattern match:
UserStatus : [
LoggedIn(User),
Inactive,
Deleted,
]
checkUserActivity : UserStatus -> Result _ _
checkUserActivity = \userStatus ->
LoggedIn user = userStatus ?? return Err NotLoggedIn
checkStatus(user)
although I don't know what it would mean if you didn't return after the ?? in that :laughing:
I guess you'd be expected to provide an alternative user or something
seems confusing
anyway, I gotta go to bed, but it's an interesting topic!
Last updated: Jun 16 2026 at 16:19 UTC