Stream: ideas

Topic: alternative to "then" for if/then/else?


view this post on Zulip Richard Feldman (Oct 14 2022 at 17:25):

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

view this post on Zulip Richard Feldman (Oct 14 2022 at 17:25):

I've wondered if we could reclaim then from if ... then in the same way

view this post on Zulip Richard Feldman (Oct 14 2022 at 17:26):

one idea I had was to use -> because in lambdas and when branches, that introduces a new nested scope

view this post on Zulip Richard Feldman (Oct 14 2022 at 17:27):

so something like if Str.isEmpty str -> instead of if Str.isEmpty str then

view this post on Zulip Richard Feldman (Oct 14 2022 at 17:28):

but at that point else would break the pattern, so maybe it should become else -> - which feels a bit weird

view this post on Zulip Richard Feldman (Oct 14 2022 at 17:28):

another option would be to use parens instead of then, e.g. if (Str.isEmpty Str)

view this post on Zulip Richard Feldman (Oct 14 2022 at 17:29):

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:

view this post on Zulip Richard Feldman (Oct 14 2022 at 17:30):

there's also : instead of -> like Python does, but -> would feel more consistent with the rest of the language to me

view this post on Zulip Richard Feldman (Oct 14 2022 at 17:30):

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

view this post on Zulip Richard Feldman (Oct 14 2022 at 17:32):

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

view this post on Zulip Richard Feldman (Oct 14 2022 at 17:32):

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:

view this post on Zulip Anton (Oct 14 2022 at 17:39):

The regular then is my favorite. -> feels strange

view this post on Zulip Brian Carroll (Oct 14 2022 at 17:43):

How often do you want a variable called then?

view this post on Zulip Brian Carroll (Oct 14 2022 at 17:43):

I suppose you could have timestamps called now and then or something

view this post on Zulip Brendan Hansknecht (Oct 14 2022 at 17:47):

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

view this post on Zulip Brian Hicks (Oct 14 2022 at 18:21):

bracketed scopes? :troll:

view this post on Zulip Brian Carroll (Oct 14 2022 at 19:05):

I suppose you could have timestamps called now and then or 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:

view this post on Zulip Luke Boswell (Oct 14 2022 at 23:16):

What about something like C ie x == 1 ? Bool.true : Bool.false

view this post on Zulip Luke Boswell (Oct 15 2022 at 00:00):

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

view this post on Zulip Brendan Hansknecht (Oct 15 2022 at 00:59):

That on multi lines looks so weird. Also, so hard to spot the :. Needs a bolder font.

view this post on Zulip jan kili (Oct 16 2022 at 01:16):

Out of curiosity, how ugly/elegant is this?
x = toggle Str.isEmpty myStr Yes 42 No 43

view this post on Zulip jan kili (Oct 16 2022 at 01:18):

We could even eliminate all relevant keywords by adding one built-in function:
x = Bool.either Str.isEmpty myStr True 42 False 43

view this post on Zulip jan kili (Oct 16 2022 at 01:19):

I have no strong opinion on the vocabulary here (toggle/either/Yes/No/True/False), more curious about eliminating keywords with reusable tags

view this post on Zulip jan kili (Oct 16 2022 at 01:20):

I hope it wouldn't need parentheses...

view this post on Zulip jan kili (Oct 16 2022 at 01:29):

...but parentheses might help readability TBH, especially when longer than this example

x =
    myStr
    |> Str.isEmpty
    |> Bool.either (IfTrue 42) (IfFalse 43)

view this post on Zulip jan kili (Oct 16 2022 at 01:39):

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...

view this post on Zulip jan kili (Oct 16 2022 at 01:41):

Maybe I'm missing that desugaring would prevent compilation optimizations or something, that's beyond me :sweat_smile:

view this post on Zulip jan kili (Oct 16 2022 at 01:44):

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.

view this post on Zulip jan kili (Oct 16 2022 at 02:03):

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

view this post on Zulip Brendan Hansknecht (Oct 16 2022 at 02:55):

Weird but quite interesting

view this post on Zulip Brendan Hansknecht (Oct 16 2022 at 02:55):

I really like the idea of pipelined when

view this post on Zulip Brendan Hansknecht (Oct 16 2022 at 02:57):

I think Bool.either is quite unreadable without the parens.

view this post on Zulip Anton (Oct 16 2022 at 07:33):

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 ->.

view this post on Zulip Brian Carroll (Oct 16 2022 at 12:33):

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.

view this post on Zulip Georges Boris (Oct 16 2022 at 15:13):

We could also just don't have if statements :sweat_smile: - how bold would that be?

view this post on Zulip Georges Boris (Oct 16 2022 at 15:13):

when condition is
  Bool.true -> ...
  Bool.false -> ...

view this post on Zulip Folkert de Vries (Oct 16 2022 at 15:29):

3 keywords removed in one fell swoop

view this post on Zulip Folkert de Vries (Oct 16 2022 at 15:29):

though you can have Foo if condition patters, so only two actually I guess

view this post on Zulip Chris Duncan (Oct 16 2022 at 15:48):

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.

view this post on Zulip Georges Boris (Oct 16 2022 at 15:55):

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 -> ...
  _ -> ...

view this post on Zulip jan kili (Oct 16 2022 at 16:00):

@Chris Duncan it actually used to be until very recently, but it was problematic

view this post on Zulip jan kili (Oct 16 2022 at 16:00):

Just saying True didn't guarantee you were making a Bool

view this post on Zulip jan kili (Oct 16 2022 at 16:03):

The recent change was just to wrap them with an opaque type, to distinguish them from generic tags

view this post on Zulip jan kili (Oct 16 2022 at 16:04):

@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

view this post on Zulip jan kili (Oct 16 2022 at 16:15):

I'm not convinced we can ignore boolean special casing, but I'm curious to see!

view this post on Zulip Georges Boris (Oct 16 2022 at 17:16):

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.

view this post on Zulip Johannes Vollmer (Oct 16 2022 at 21:49):

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

view this post on Zulip Luke Boswell (Oct 17 2022 at 00:01):

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

view this post on Zulip Anton (Oct 17 2022 at 06:33):

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

view this post on Zulip Luke Boswell (Oct 17 2022 at 06:50):

I updated the examples I have with your suggestions @Anton is this closer to what you were thinking? gist

view this post on Zulip Anton (Oct 17 2022 at 07:33):

Yes indeed, thanks @Luke Boswell :)

view this post on Zulip Georges Boris (Oct 17 2022 at 08:27):

@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?

view this post on Zulip Anton (Oct 17 2022 at 08:41):

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.

view this post on Zulip Georges Boris (Oct 17 2022 at 08:48):

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 -> ...
    _ -> ...

view this post on Zulip Anton (Oct 17 2022 at 08:50):

Excellent suggestion, I like it a lot!

view this post on Zulip Luke Boswell (Oct 17 2022 at 09:28):

Updated my examples again just using if statement

view this post on Zulip Georges Boris (Oct 17 2022 at 10:20):

@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 _ -> ...

view this post on Zulip Gabriela (Oct 17 2022 at 10:28):

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.

view this post on Zulip Anton (Oct 17 2022 at 10:37):

Thanks for weighing in @Gabriela :)

  1. On the spacing issue I think we'd be fine with our current slightly different form.
  2. The second point is valid but not unsolvable I believe.
  3. I think we would cut if/then/else from the language if the current discussions can have us end up with something cleaner.

I also don't mind then being a reserved word.

view this post on Zulip Richard Feldman (Oct 17 2022 at 11:07):

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

view this post on Zulip Georges Boris (Oct 17 2022 at 11:21):

@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

view this post on Zulip Anton (Oct 17 2022 at 11:39):

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.

view this post on Zulip Brendan Hansknecht (Oct 17 2022 at 15:02):

I think part of the problem with a lot of the suggestions is that they:

  1. Have sweeping effects that are not noted here
  2. They are solving different problems. One may be nice for pipelining another for consistent looking conditionals, another for simplicity.

view this post on Zulip Anton (Oct 17 2022 at 15:04):

Yeah, we did digress from the original question :p

view this post on Zulip Brendan Hansknecht (Oct 17 2022 at 15:06):

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