Stream: ideas

Topic: allow def-blocks without parens in args


view this post on Zulip Richard Feldman (Jan 06 2023 at 01:48):

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)}

            """

view this post on Zulip Richard Feldman (Jan 06 2023 at 01:49):

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)}

        """

view this post on Zulip Richard Feldman (Jan 06 2023 at 01:49):

so basically the argument to Str.concat is a def-expression

view this post on Zulip Richard Feldman (Jan 06 2023 at 01:49):

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)}

            """
        )

view this post on Zulip Richard Feldman (Jan 06 2023 at 01:50):

so really the question comes down to: what are people's thoughts on these parens being required vs not?

view this post on Zulip Kevin Gillette (Jan 06 2023 at 02:03):

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.

view this post on Zulip Brendan Hansknecht (Jan 06 2023 at 02:05):

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.

view this post on Zulip Kevin Gillette (Jan 06 2023 at 02:07):

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.

view this post on Zulip Kevin Gillette (Jan 06 2023 at 02:08):

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.

view this post on Zulip Brendan Hansknecht (Jan 06 2023 at 02:12):

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.

view this post on Zulip Kevin Gillette (Jan 06 2023 at 02:13):

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

view this post on Zulip Kevin Gillette (Jan 06 2023 at 02:15):

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.

view this post on Zulip Kevin Gillette (Jan 06 2023 at 02:15):

3-line nested lambdas are already at the limit of what typically looks reasonable to me though.

view this post on Zulip Kevin Gillette (Jan 06 2023 at 02:17):

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")

view this post on Zulip Brendan Hansknecht (Jan 06 2023 at 02:23):

Fair, calling it inline is a stretch, but I more meant in pipeline.

view this post on Zulip Brendan Hansknecht (Jan 06 2023 at 02:24):

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.

view this post on Zulip Brendan Hansknecht (Jan 06 2023 at 02:34):

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