I generally have a preference for minimizing reserved keywords in order to enable them in userspace. For example, it's very intentional that we have syntax like Foo : ... for type aliases and Blah := ... for opaque types, as opposed to the traditional type keyword for one or both of these - it means you can name a variable type in Roc
I've wondered if we could reclaim then from if ... then in the same way
one idea I had was to use -> because in lambdas and when branches, that introduces a new nested scope
so something like if Str.isEmpty str -> instead of if Str.isEmpty str then
but at that point else would break the pattern, so maybe it should become else -> - which feels a bit weird
another option would be to use parens instead of then, e.g. if (Str.isEmpty Str)
anything that could eliminate then could also facilitate reclaiming is from when ... is but I have to admit, I'm fond of how that one reads :big_smile:
there's also : instead of -> like Python does, but -> would feel more consistent with the rest of the language to me
also there's the CoffeeScript style where it's based on indentation: you just don't have a then, but rather the next line has to be indented
this would mean multiline if conditionals would need parens, as would single-line entire if expressions, e.g. x = if (Str.isEmpty str) 42 else 43
anyway, I'm curious what others think about this! I don't have very strong feelings about this, but it's been kicking around in my head for a long time, so I figured I'd write it down and get some other perspectives on it :big_smile:
The regular then is my favorite. -> feels strange
How often do you want a variable called then?
I suppose you could have timestamps called now and then or something
Could also maybe have a function call then. Might make a lot more sense with the module header Task.then...idk
That said, I like if ... then as well. Roc has made me really dislike parentheses. Also, I agree that -> feels strange and think the overloading may be confusing to new users
bracketed scopes? :troll:
I suppose you could have timestamps called
nowandthenor something
Anyway, there are millions of other names. Having one more available is a rather small improvement! And I like it as a keyword! :big_smile:
What about something like C ie x == 1 ? Bool.true : Bool.false
Expanded into some examples... to see how it might work.
## if single line
if myFunction x ? returnMe : otherwiseMe
## if multi line
if myFunction x ?
returnMe
:
otherwiseMe
## when single line
when myFunction ? 1 -> x, 2 -> y, 3 -> z
## when multiline
when myFunction ?
1 -> x
2 -> y
3 -> z
## larger example
walkUtf8WithIndexHelp = \string, state, step, index, length ->
if index < length ?
byte = Str.getUnsafe string index
newState = step state byte index
walkUtf8WithIndexHelp string newState step (index + 1) length
:
state
## even larger
walkScalarsUntilHelp = \string, state, step, index, length ->
if index < length ?
{ scalar, bytesParsed } = getScalarUnsafe string index
when step state scalar ?
Continue newState ->
walkScalarsUntilHelp string newState step (index + bytesParsed) length
Break newState ->
newState
:
state
## another example
findFirst = \list, pred ->
callback = \_, elem ->
if pred elem ?
Break elem
:
Continue {}
when List.iterate list {} callback ?
Continue {} -> Err NotFound
Break found -> Ok found
That on multi lines looks so weird. Also, so hard to spot the :. Needs a bolder font.
Out of curiosity, how ugly/elegant is this?
x = toggle Str.isEmpty myStr Yes 42 No 43
We could even eliminate all relevant keywords by adding one built-in function:
x = Bool.either Str.isEmpty myStr True 42 False 43
I have no strong opinion on the vocabulary here (toggle/either/Yes/No/True/False), more curious about eliminating keywords with reusable tags
I hope it wouldn't need parentheses...
...but parentheses might help readability TBH, especially when longer than this example
x =
myStr
|> Str.isEmpty
|> Bool.either (IfTrue 42) (IfFalse 43)
Actually, regardless of what we do with if/then/else & when/is/->, we could add built-in functions that can do the same thing (like Bool.either and, idk, Logical.switch). Wait, is having two ways to do something an antipattern? It's almost like desugaring...
Maybe I'm missing that desugaring would prevent compilation optimizations or something, that's beyond me :sweat_smile:
I mainly hope that the canonical if/else syntax is optimized for pipelines, and unrelatedly I also like that tags can make things more self-documenting.
I can't resist undermining my own argument by taking this concept to an uncomfortable extreme:
x =
myStr
|> Str.len
|> List.when [
Equals 0 Then "empty!",
LessThan 10 Then "short.",
Matches (\y -> y % 100 == 1) Then
Else "long enough",
]
List.when a b : a, List [ Equals a Then b, Matches a Then b, ... other options ... , Else b ] -> b
Weird but quite interesting
I really like the idea of pipelined when
I think Bool.either is quite unreadable without the parens.
Wait, is having two ways to do something an antipattern? It's almost like desugaring...
With syntactic sugar the code is usually noticeably more compact/readable, I don't feel like this is the case here.
The List.when looks very dense. I think the current when creates some nice visual structure with the ->.
Regarding the list of tags in the List.when example: It's easy to forget a case, and there's no good way for the compiler to detect that in general. Because the list of tags could be built up from arbitrarily complex code. In my opinion this approach won't work.
We could also just don't have if statements :sweat_smile: - how bold would that be?
when condition is
Bool.true -> ...
Bool.false -> ...
3 keywords removed in one fell swoop
though you can have Foo if condition patters, so only two actually I guess
I'm surprised that Bool isn't defined as [True, False]. I'm guessing it has to do with how these values are represented in memory.
multiple if else statements could also be easily reproduced with guards in pattern matching
when x is
_ if x > 3 -> ...
_ if x < 1 || x > 0 -> ...
_ -> ...
@Chris Duncan it actually used to be until very recently, but it was problematic
Just saying True didn't guarantee you were making a Bool
The recent change was just to wrap them with an opaque type, to distinguish them from generic tags
@Georges Boris a key hypothesis here might be that by making complex pattern matching easier, it's less important to special case simpler pattern matching
I'm not convinced we can ignore boolean special casing, but I'm curious to see!
reviewing my last message about multiple if/else using guards - maybe a possible approach when interested in guards only would be something like this?
when
x > 1 -> ...
x < 0 -> ...
_ -> ...
overloading when with 0-arity so we don't need to ignore the pattern matched value.
combine guards with |>?
availableSnacks
?> List.isEmpty -> "No snacks here :("
?> eq ["Fries", "Chips"] -> "Potato Snacks available"
?> "we got some other snacks yay"
might want to replace ?> with something prettier though
also the else syntax feels like something is missing
but what would you do if you already have a boolean, would you have to use isStatic ?> eq True -> "it's static"? maybe this is not generic enough, especially since you are forced to operate on one single value
also I just saw that this is just a smaller syntax of @JanCVanB previous idea
Some cool ideas. I combined them into a larger example to see what it might look like. I'm not sure if this is realistic, I might be mixing two different ideas here with guards, but it felt nice enough to type and work with. I tried different character combinations other than <>, like --- but this felt the best and visually distinguishes the else case well enough I think. The single line if statement seems weird, but maybe a convention to split it out is clearer anyway.
## if single line becomes
when f x <> Bool.true -> returnMe <> Bool.false -> otherwiseMe
## if multi line becomes
when f x
<> Bool.true -> returnMe
<> Bool.false -> otherwiseMe
## when multiline
when List.len x
<> y < 25 || y > 25 -> NotToday
<> y == 25 -> Bingo
when availableSnacks
<> List.isEmpty -> "No snacks"
<> eq ["Fries", "Chips"] -> "Potato snacks available"
<> _ -> "other snacks"
when myList
<> [] -> "Is empty"
<> [oneValue] -> "Only one"
<> others -> "All the things"
## larger example
walkUtf8WithIndexHelp = \string, state, step, index, length ->
when index < length
<> Bool.true ->
byte = Str.getUnsafe string index
newState = step state byte index
walkUtf8WithIndexHelp string newState step (index + 1) length
<> Bool.false ->
state
## even larger
walkScalarsUntilHelp = \string, state, step, index, length ->
when index < length
<> Bool.true ->
{ scalar, bytesParsed } = getScalarUnsafe string index
when step state scalar
<> Continue newState ->
walkScalarsUntilHelp string newState step (index + bytesParsed) length
<> Break newState ->
newState
<> Bool.false ->
state
## And another
findFirst = \list, pred ->
callback = \_, elem ->
when pred elem
<> Bool.true -> Break elem
<> Bool.false -> Continue {}
when List.iterate list {} callback
<> Continue {} -> Err NotFound
<> Break found -> Ok found
I do like this idea. ?> would be my preferred operator, the ? is a good fit for checking boolean conditions.
One weakness I see is that when using Bool.true you need to look elsewhere to find the condition, whereas with an if the condition is right there. But then again, for a regular else you also need to search for the condition so perhaps it doesn't matter much.
We could stylistically recommend doing:
when
?> f x ->
returnMe
?> ! f x ->
otherwiseMe
And desugar it so the condition is only checked once
I updated the examples I have with your suggestions @Anton is this closer to what you were thinking? gist
Yes indeed, thanks @Luke Boswell :)
@Anton out of curiosity - is there a practical reason for the need of an operator at all or is it more of a stylistic choice?
Good observation, the operator could be left out but it could be nicer to be able to see immediately that we're testing a condition instead of regular matching.
just jamming ideas here - what if the if keyword was already this differentiator and our if acted like a when without the pattern matching?
if
x > 0 -> ...
x < 0 -> ...
_ -> ...
Excellent suggestion, I like it a lot!
Updated my examples again just using if statement
@Luke Boswell I believe your examples are more like when expressions where we are giving a value and matching it against things that may or not be true.
In the example above the if doesn't take any arguments and just goes through guard checks until matching one.
In this scenario, we could have things like:
# bool checks are strictly simpler
when x is
Bool.true -> ...
Bool.false -> ...
if
x -> ...
_ -> ...
# when expressions when only using guard statements are simpler
when x is
_ if x > 0 -> ...
_ if x > 1 || x < 1 -> ...
_ -> ...
if
x > 0 -> ...
x > 1 || x < 1 -> ...
_ -> ...
# when expressions when matching + guarding are useful as always
when result is
Ok value if value > 1 -> ...
Ok value -> ...
Err _ -> ...
Elm used to have multi-way if, but it was removed. I know Roc is not Elm, but there is an article explaining the reason:
https://elm-lang.org/news/compilers-as-assistants#removing-syntax
I don't think then being a reserved word is a problem, the rare cases I needed I used then_ instead.
Thanks for weighing in @Gabriela :)
I also don't mind then being a reserved word.
interesting to reread my past self from the link in that Elm blog post :big_smile:
https://github.com/elm-lang/elm-plans/issues/3#issuecomment-120782368
@Richard Feldman it's funny because it made sense then but at the same time pattern matching and piping became a lot more common recently - especially in FP space, right? so the same argument would probably not be as strong when talking about Roc imo
I would agree that there is a cost to doing this differently than usual but I see the conditions lining up nicely as a big benefit.
I think part of the problem with a lot of the suggestions is that they:
Yeah, we did digress from the original question :p
Yeah, some just feel like comparing apples it oranges ... Like maybe we do want to consider all of those now, but it definitely opens a flood gate
Last updated: Jun 16 2026 at 16:19 UTC