consider this code:
|> \buf ->
retType =
fieldTypes
|> List.map \ft -> "&\(ft)"
|> toRustStr
retExpr =
fieldAccesses
|> List.map \fa -> "&\(fa)"
|> toRustStr
Str.concat
buf
"""
\(indent)/// Since `\(name)` only has one tag (namely, `\(tagName)`),
\(indent)/// convert it to `\(tagName)`'s payload.
\(indent)pub fn as_\(tagName)(self) -> \(retType) {
\(indent) \(retExpr)
\(indent)}
"""
what if we could rewrite it like this?
|> Str.concat
retType =
fieldTypes
|> List.map \ft -> "&\(ft)"
|> toRustStr
retExpr =
fieldAccesses
|> List.map \fa -> "&\(fa)"
|> toRustStr
"""
\(indent)/// Since `\(name)` only has one tag (namely, `\(tagName)`),
\(indent)/// convert it to `\(tagName)`'s payload.
\(indent)pub fn as_\(tagName)(self) -> \(retType) {
\(indent) \(retExpr)
\(indent)}
"""
so basically the argument to Str.concat is a def-expression
this doesn't parse today, but you can get the same functionality with parens like so:
|> Str.concat
(
retType =
fieldTypes
|> List.map \ft -> "&\(ft)"
|> toRustStr
retExpr =
fieldAccesses
|> List.map \fa -> "&\(fa)"
|> toRustStr
"""
\(indent)/// Since `\(name)` only has one tag (namely, `\(tagName)`),
\(indent)/// convert it to `\(tagName)`'s payload.
\(indent)pub fn as_\(tagName)(self) -> \(retType) {
\(indent) \(retExpr)
\(indent)}
"""
)
so really the question comes down to: what are people's thoughts on these parens being required vs not?
I find multi-line wrapping parens to be the bane of quality in every language I've encountered thus far (to both more and lesser extent for LISPs).
Multi-line lists (or other data-structure literals)? Great! Lists tend to be have structurally--and visually--similar elements, and thus visually similar lines of code (which makes for rather easy reading).
But these parens don't add have that quality at all: each line can be arbitrary code with arbitrary structure, and by the time a reader reaches the closing parenthesis, they may have lost the context that indicates what the parentheses meant. If Roc is a language with significant indentation, I figure we might as well embrace it (or abandon it), and use indentation alone to represent the code structure here.
My gut feeling is that i don't like it. Hard to track the actual args of Str.concat.
Kinda feels like you just crammed arbitrary code where it doesn't belong. I totally get the goal and maybe would fine it useful, but not sold on readability, especially if it could be nested.
I'm not terribly convinced about the style of making pipelines that look like this, however. It starts out as "there's a simple pipeline of x |> y |> Str.concat," but then digresses into something that could be dozens of lines long, and quite often un-digressing ("now where was I?") back into the outer pipeline.
In these cases, I find the nested code is hard to follow, and I often have doubts (due to my inexperience with Roc) if the resumption of the outer pipeline is really the outer pipeline, or if it's just mis-indented code that's still considered part of any inner processing. I feel better (mixed feelings, rather than positive) when the indented portion of the pipeline forms strictly the final part of the pipeline. In any case, I'd usually rather see the above as a separate function, though that can be a hard tradeoff to make if there are a lot of closed-over variables used by the inner code.
Assuming you can give it a clear name, i agree that adding a separate function will always be more readable and better code. If you end up having a change of these kinds of functions and the names aren't great, i think it just leads to a feel of spaghetti code where you constantly are jumping between functions to understand what is actually happening. This is often why I pick the inline lambda even though it is verbose in a pipeline.
Inline lambdas that are comfortably one-liners, I agree, are a non-distracting choice (it's easy enough to ignore the lambdas to read the outer structure, and then look at the lambda code to see what it's doing if needed).
Backpassing also qualifies, in my mind, as readable, since the experienced reader knows, by definition, that the rest of the function pertains to that lambda or any sub-processing within, which allows the reader to free up, rather than hold onto, a lot of mental context.
3-line nested lambdas are already at the limit of what typically looks reasonable to me though.
And code written with 4+ line nested lambdas very much feels like a stream-of-consciousness or write-once style (i.e. "hmmmm. I wrote this 2 weeks ago, but let me study it again to see what it does. Give me 5 minutes")
Fair, calling it inline is a stretch, but I more meant in pipeline.
That said, I definitely don't find this code write once. You just follow it one lambda at a time. It is easier to edit than code with x1, x2, x3, etc.
I think it really only works this way with clear state that is moving from one stage to the next. If the state changes type regularly or complexity, i think that is probably write once, but if the state is really just an accumulator, i think this form of pipelining is readable once you are use to it.
Last updated: Jun 16 2026 at 16:19 UTC