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
Option A: above
Option B: define the \x -> ... function as its own named expression, then use it in the pipeline as |> namedFunction
Option C: (hopefully some better existing technique I'm missing)
Option D: add a syntax sugar like
foo
|> bar
|> when
A a ->
aaa a ...
B b ->
bbb b ...
|> baz
Option E: (hopefully some better new proposal I'm missing)
Does anyone else feel aesthetic/ergonomic friction with when clauses in pipelines?
I feel the same friction in Haskell and Elm all the time! I use the same techniques you mentioned.
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
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
Would issue 4139 do anything more for this than just removing the parentheses?
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!
Hmm, I'm not entirely convinced. I think it is an antipattern to mix when and pipes in general.
Because you're working on a different level of abstraction with the two
So I prefer giving a name to the anonymous function that contains the when
In Haskell it is called LambdaCase language extension, and I am big fan of it.
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
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)
I believe the Elixir code linter (Credo) gives you a readability warning when you do this.
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).
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.
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