Stream: ideas

Topic: Syntax sugar for "piped when"?


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

I find myself often wanting to put a when foo is ... expression in the middle of a pipeline, but it never looks good. Is there a way to make this more ergonomically/aesthetically pleasing, or am I missing an existing technique?

foo
|> bar
|> (\x ->
    when x is
        A a ->
            aaa a ...
        B b ->
            bbb b ...)
|> baz

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

Option A: above

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

Option B: define the \x -> ... function as its own named expression, then use it in the pipeline as |> namedFunction

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

Option C: (hopefully some better existing technique I'm missing)

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

Option D: add a syntax sugar like

foo
|> bar
|> when
    A a ->
        aaa a ...
    B b ->
        bbb b ...
|> baz

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

Option E: (hopefully some better new proposal I'm missing)

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

Does anyone else feel aesthetic/ergonomic friction with when clauses in pipelines?

view this post on Zulip Brian Carroll (Oct 04 2022 at 05:13):

I feel the same friction in Haskell and Elm all the time! I use the same techniques you mentioned.

view this post on Zulip Ayaz Hafiz (Oct 04 2022 at 05:24):

OCaml has a nice answer to the above with Option D. In OCaml

let f x = match x with | A -> ... | B -> ...

and

let f = function | A -> ... | B -> ...

are the same. I very frequently use the latter. Here is a file with some examples in my code: https://github.com/ayazhafiz/plts/blob/base/gtlc/infer.ml

view this post on Zulip Ayaz Hafiz (Oct 04 2022 at 05:26):

That is to say, syntax sugar a-la D would be nice, but I wonder if the inconvenience would be lessened with https://github.com/roc-lang/roc/issues/4139

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

Would issue 4139 do anything more for this than just removing the parentheses?

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

Nice, yes, but I think the explicit function syntax (\x -> when x ...) is responsible for at least half of the discomfort here. Maybe I'm off-base on why this is awkward, though!

view this post on Zulip Qqwy / Marten (Oct 04 2022 at 11:14):

Hmm, I'm not entirely convinced. I think it is an antipattern to mix when and pipes in general.

view this post on Zulip Qqwy / Marten (Oct 04 2022 at 11:14):

Because you're working on a different level of abstraction with the two

view this post on Zulip Qqwy / Marten (Oct 04 2022 at 11:15):

So I prefer giving a name to the anonymous function that contains the when

view this post on Zulip Zeljko Nesic (Oct 04 2022 at 13:02):

In Haskell it is called LambdaCase language extension, and I am big fan of it.

view this post on Zulip Kilian Vounckx (Oct 05 2022 at 06:51):

As Zeljko said, when you enable the LambdaCase extension in haskell, \x -> case x of becomes \case.
Unison has a similar keyword cases. It works the same as \case. I like both and use them more often then the explicit version of naming a variable and then immediately pattern matching

view this post on Zulip Georges Boris (Oct 05 2022 at 09:10):

In elixir we can think of expressions like functions so placing them into pipes just gives them the piped value as the first argument (like we do in Roc, so the semantic would still work)

e.g

|> then(fn x ->
  case x do
    :ok -> ...
    :error -> ...
  end
)

vs

|> case do
    :ok -> ...
    :error -> ...
  end

tho I rarely use it in the middle of a pipe (as it is quite verbose... I prefer named functions)

view this post on Zulip Qqwy / Marten (Oct 06 2022 at 16:36):

I believe the Elixir code linter (Credo) gives you a readability warning when you do this.

view this post on Zulip Kevin Gillette (Nov 03 2022 at 15:24):

I'll assert that any situation where a pipeline indents (especially if it dedents later) causes exceptional harm to readability, since it visually derails the pacing of what's being read, and ends up with low-density information (the multi-line when expression) distracting away from high density information (simple single-line transforms).

This is thematically equivalent to failing to split a large function; or deeply nesting if-else conditions; or putting a one-liner else clause at the end of a much larger if (it requires more mental context than inverting the condition and putting the short branch first).

The proposed syntax sugar, in my mind, helps marginally by hiding semantically unnecessary details, but doesn't do anything to avoid derailing the reader, and thus overall doesn't do anything to solve the larger issue.

With the language as-is, I'd probably just hoist that "when" function into a variable.

Regarding potential changes, one thing that could help, albeit with limitations (though possibly something we could do without changes to the language) is provide a higher order function, better naming TBD, like:

(My Roc is rather rusty)

ifthen : [a]x, (a -> b) -> [b]x
ifthen = \v, fn ->
    when v is
        a -> fn a
        _ -> v

Following the OP example, the use of this could look like:

foo
|> bar
|> ifthen (\A a -> aaa a)
|> ifthen (\B b -> bbb b)
|> baz

The intent is that ifthen would transform one of a union of input types if it matches the input of the function, but otherwise pass through the other tags. Chaining these, in some cases, could be equivalent to a when expression. One downside is that if any ifthen in a chain can consume a type previously produced, then the semantics deviate from when expressions (which may or may not be desirable: more powerful, but more complex). The other downside is that you'd need to ensure the input is a tag union if it wasn't already, and you may need to unwrap the output (since a series of ifthen applications may trim a tag union down to a single member).

view this post on Zulip Kevin Gillette (Nov 03 2022 at 15:33):

Of course, if we were open to amending the language to help with this, we could perhaps allow a chain of shorthand when expressions:

foo
|> bar
|> when A a -> aaa a
|> when B b -> bbb b
|> baz

This would no longer require the input to be a tag union (you could do when 5 for example), but it would, in effect, either require that a pipeline of consecutive whens be special cased (considered together) to avoid type wrangling complexity, or it would require use of tag unions in many cases anyways, since when 5 -> 6 |> when 6 -> 7 would yield a 7 if given a 5.

Also, if not special-cased, a _ match wouldn't be usable, since it would clobber the result of any previous matches.

view this post on Zulip Maksim Volkau (Nov 07 2024 at 11:18):

Adding my take.
I have played with the parser and implemented a binary operator for when in my fork.
For now, it uses '~' symbol, which is not utilized yet and simplifies the implementation.
In a sense, it looks similar to the |> which is also a "pseudo"-binary operator in Roc.

List.map [1, 2, 3] \x -> x ~
    1 -> 42
    _ -> 13

It also combines with the other fun feature, a closure shortcut:

List.map [1, 2, 3] \~
    1 -> 42
    _ -> 13

See some more examples here: https://roc.zulipchat.com/#narrow/channel/304641-ideas/topic/Shorthands.20for.20nested.20records/near/480527148


Last updated: Jun 16 2026 at 16:19 UTC