Stream: ideas

Topic: ✔ /tmp/scratchpad/wsa-sd-syntax-proposals


view this post on Zulip jan kili (Feb 07 2025 at 20:32):

@Eli Dowling How about a separate thread for us to sort and organize existing proposals before posting them neatly in #ideas > WSA-static dispatch-syntax proposals ?

view this post on Zulip jan kili (Feb 07 2025 at 20:33):

This is now a messy noisy desktop for collaboration. Please don't link to any message in here.

view this post on Zulip Eli Dowling (Feb 07 2025 at 20:34):

#ideas > ✔ Static dispatch without parens and commas
I was going to have a look in that thread. my train is arriving though so I'm off for a bit

view this post on Zulip jan kili (Feb 07 2025 at 20:34):

Cool, I'll pick that up then

view this post on Zulip jan kili (Feb 07 2025 at 20:34):

I'm wanting to format a bunch of code snippets but my brain is slightly foggy today, so that helps me find content to organize

view this post on Zulip jan kili (Feb 07 2025 at 20:46):

In that thread,

[1,2,3]->map \n -> n + -1
aSet->insert 42
23->to_u64
allItems
|> _.mapTry \item -> validate item
|> _.len
initialData
|> .map .innerData
|> .aggregate
|> .firstField
|> .secondField
initialData
.> map .>innerData
.> aggregate
|> .firstField
|> .secondField

so the idea is that this:

initial_data
|> .map .inner_data
|> .aggregate
|> .first_field
|> .second_field

is a big improvement over this?

initial_data
.map(.inner_data)
.aggregate()
.first_field
.second_field

and expressed preference for the latter

view this post on Zulip jan kili (Feb 07 2025 at 20:55):

When organizing side-by-sides, I wonder how we should approach complications like:

view this post on Zulip Sam Mohr (Feb 07 2025 at 20:56):

I don't think there's much want for ? in WSA, I'd just focus on try for WSA and ? for PNC

view this post on Zulip jan kili (Feb 07 2025 at 21:05):

Related:

JanCVanB said:

main! = |_|
    "./input.txt"
    |> Path.from_str
    |> try Path.read_bytes!
    # |> dbg
    |> try Foo.from_bytes
    # |> dbg
    |> transform
    # |> dbg
    |> try Foo.to_bytes
    # |> dbg
    |> try Path.write_bytes! (Path.from_str "./output.txt")

    Stdout.line! "🥳 See ./output.txt"

[vs]

main! = |_|
    "./input.txt"
        .(Path.from_str)()
        .read_bytes!()?
        # .(dbg)()
        .(Foo.from_bytes)()?
        # .(dbg)()
        .transform()
        # .(dbg)()
        .to_bytes()?
        # .(dbg)()
        .(Path.write_bytes!)(Path.from_str("./output.txt"))

    Stdout.line!("🥳 See ./output.txt")

(this wasn't a WSA SD syntax proposal, just asking about newline handling, but it could be useful as we pick example(s) for side-by-side-ing)

view this post on Zulip jan kili (Feb 07 2025 at 21:16):

I'm curious what that multiline+effectful (but not resultful?) example looks like with some of the proposals above:

main! = |_|
    "./input.txt"
    |> Path.from_str
    ||> try read_bytes!
    # |> dbg
    |> try Foo.from_bytes
    # |> dbg
    |> transform
    # |> dbg
    ||> try to_bytes
    # |> dbg
    |> try Path.write_bytes! (Path.from_str "./output.txt")

    Stdout.line! "🥳 See ./output.txt"
main! = |_|
    "./input.txt"
    |> Path.from_str
    |> try _.read_bytes!
    # |> dbg
    |> try Foo.from_bytes
    # |> dbg
    |> transform
    # |> dbg
    |> try _.to_bytes
    # |> dbg
    |> try Path.write_bytes! (Path.from_str "./output.txt")

    Stdout.line! "🥳 See ./output.txt"
main! = |_|
    "./input.txt"
    |> Path.from_str
    .> try read_bytes!
    # |> dbg
    |> try Foo.from_bytes
    # |> dbg
    |> transform
    # |> dbg
    .> try to_bytes
    # |> dbg
    |> try Path.write_bytes! (Path.from_str "./output.txt")

    Stdout.line! "🥳 See ./output.txt"
main! = |_|
    "./input.txt"
    |> Path.from_str
    |> try .read_bytes!
    # |> dbg
    |> try Foo.from_bytes
    # |> dbg
    |> transform
    # |> dbg
    |> try .to_bytes
    # |> dbg
    |> try Path.write_bytes! (Path.from_str "./output.txt")

    Stdout.line! "🥳 See ./output.txt"

For some of these I wonder if there's a better place for try/? to go...

view this post on Zulip jan kili (Feb 07 2025 at 21:20):

Maybe it would be best to provide 2 examples (one single-line, one multi-line) for each WSA SD syntax proposal?

view this post on Zulip jan kili (Feb 07 2025 at 21:21):

Should we make both medium-high complexity? For example, having the single-line include some kind of Result handling? and nested function calls?

view this post on Zulip jan kili (Feb 07 2025 at 21:26):

I also wonder whether we should differentiate between #ideas > WSA-static dispatch-syntax proposals being an authoritative collection of proposals for WSA+SD vs it being the final examples to defend all the merits of WSA. Perhaps we should create that second topic now, with the qualification that "for this discussion, assume WSA can do SD"? It would include a defense of |>, keystroke flow, aesthetics, and all the other stuff that removing WSA impacts.

view this post on Zulip jan kili (Feb 07 2025 at 21:28):

I wonder if a topic that doesn't contain the phrase "static dispatch" would bring out of the woodwork more folks that are like "wait, :pizza: is leaving??"

view this post on Zulip jan kili (Feb 07 2025 at 21:28):

because I've heard that mentioned as a side effect of WSA leaving

view this post on Zulip Sam Mohr (Feb 07 2025 at 21:29):

Yes, |> is gone is WSA leaves

view this post on Zulip Sam Mohr (Feb 07 2025 at 21:29):

Because there's no need for it in PNC land

view this post on Zulip jan kili (Feb 07 2025 at 21:31):

We should probably move this to another topic, but can't multiline PNC not function today without it?

view this post on Zulip jan kili (Feb 07 2025 at 21:32):

I'm worried that could give us a few months before SD lands where 150-character lines are expected lol

view this post on Zulip Sam Mohr (Feb 07 2025 at 21:33):

It causes precedence problems when you have chains of whitespace and method calls interwoven that are visually confusing

view this post on Zulip Sam Mohr (Feb 07 2025 at 21:33):

I think the item.(local_func)(args) syntax should avoid the prevalence of long method chains

view this post on Zulip jan kili (Feb 07 2025 at 21:35):

Does that land way before SD?

view this post on Zulip Sam Mohr (Feb 07 2025 at 21:35):

It's purely sugar for local_func(item, args) so it definitely could

view this post on Zulip Sam Mohr (Feb 07 2025 at 21:36):

The timeline is more of a @Anthony Bullard @Joshua Warner question

view this post on Zulip jan kili (Feb 07 2025 at 21:37):

This is what I think you have to do today to write the above example in PNC without :pizza: :

main! = |_|
    "./input.txt"
        .(Path.from_str)()
        .(Path.read_bytes!)()?
        # .(dbg)()
        .(Foo.from_bytes)()?
        # .(dbg)()
        .(Foo.transform)()
        # .(dbg)()
        .(Foo.to_bytes)()?
        # .(dbg)()
        .(Path.write_bytes!)(Path.from_str("./output.txt"))

    Stdout.line!("🥳 See ./output.txt")

so it's not impossible, just dubious.

view this post on Zulip Sam Mohr (Feb 07 2025 at 21:38):

If there are no args, you don't need the second ()

view this post on Zulip jan kili (Feb 07 2025 at 21:38):

Ooh good to know. Less dubious.

main! = |_|
    "./input.txt"
        .(Path.from_str)
        .(Path.read_bytes!)?
        # .(dbg)
        .(Foo.from_bytes)?
        # .(dbg)
        .(Foo.transform)
        # .(dbg)
        .(Foo.to_bytes)?
        # .(dbg)()
        .(Path.write_bytes!)(Path.from_str("./output.txt"))

    Stdout.line!("🥳 See ./output.txt")

view this post on Zulip jan kili (Feb 07 2025 at 21:39):

Correction to above: This isn't possible "today" because .(local) needs to land first.

view this post on Zulip jan kili (Feb 07 2025 at 21:40):

Today without :pizza: you need to invert the call order, right?

view this post on Zulip Sam Mohr (Feb 07 2025 at 21:42):

To beat a dead horse, I think this incentivizes shorter chains, which improves readability:

main! = |_|
    path =
        Path.from_str("./input.txt")
        .read_bytes!()?
        .dbg()

    foo =
        Foo.from_bytes(path)?
        .transform()
        .to_bytes()?
        .dbg()

    Path.write_bytes!(foo, Path.from_str("./output.txt"))

    Stdout.line!("🥳 See ./output.txt")

view this post on Zulip jan kili (Feb 07 2025 at 21:43):

I slightly disagree because one of the reasons I fell in love with Roc is that you don't need to add arbitrary intermediate variables because :pizza: chains were so fluid.

view this post on Zulip jan kili (Feb 07 2025 at 21:44):

But I hear you that that's doable after SD lands. Today :pizza: is still needed for multilining like that without inverting call order.

view this post on Zulip jan kili (Feb 07 2025 at 21:45):

It might be helpful to start the proposal thread with "WSA today" and "PNC today" before going into all the various things they could be when SD lands. For example, it's still undecided whether PNC will use .$

view this post on Zulip Sam Mohr (Feb 07 2025 at 21:47):

I don't think .$ is planned for usage anywhere

view this post on Zulip jan kili (Feb 07 2025 at 21:47):

PNC today:

main! = |_|
    # bytes = dbg(Path.read_bytes!(Path.from_str("./input.txt"))?)
    bytes = Path.read_bytes!(Path.from_str("./input.txt"))?
    # foo = dbg(Foo.to_bytes(dbg(transform(dbg(Foo.from_bytes(bytes)?))))?)
    foo = Foo.to_bytes(transform(Foo.from_bytes(bytes)?))?
    Path.write_bytes!(foo, Path.from_str("./output.txt"))
    Stdout.line!("🥳 See ./output.txt")

view this post on Zulip jan kili (Feb 07 2025 at 21:48):

with newlines:

main! = |_|
    # bytes = dbg(
    bytes = Path.read_bytes!(
        Path.from_str("./input.txt")
    )?

    # foo = dbg(
    foo = Foo.to_bytes(
        # dbg(
        transform(
            # dbg(
            Foo.from_bytes(bytes)?
        )
    )?

    Path.write_bytes!(
        foo,
        Path.from_str("./output.txt")
    )

    Stdout.line!("🥳 See ./output.txt")

view this post on Zulip jan kili (Feb 07 2025 at 21:50):

I don't really mean to attack PNC with these examples, but I don't want to do it any favors by giving it the benefit of the doubt of syntax that hasn't landed yet. (because WSA isn't getting that same benefit of the doubt)

(eh, I suppose my inclusion of dbg comments is a clear WSA-biased element of attacking PNC)

view this post on Zulip jan kili (Feb 07 2025 at 21:51):

When showing WSA+SD though, we should show that forecasted PNC+SD as the comparison!

view this post on Zulip jan kili (Feb 07 2025 at 21:52):

WSA today:

main! = |_|
    "./input.txt"
    |> Path.from_str
    |> try Path.read_bytes!
    # |> dbg
    |> try Foo.from_bytes
    # |> dbg
    |> transform
    # |> dbg
    |> try Foo.to_bytes
    # |> dbg
    |> try Path.write_bytes! (Path.from_str "./output.txt")

    Stdout.line! "🥳 See ./output.txt"

view this post on Zulip Eli Dowling (Feb 07 2025 at 21:53):

I realised today while implementing the tree sitter parser. That it's ambiguous whether the proposed local function syntax in PNC is a function returning a function or a local function call

like can you tell me what this does?

add_maker= |mather|
   |number| mather(number)

multiplier = |a| a*10

add_maker.(multiplier)(10)

view this post on Zulip Sam Mohr (Feb 07 2025 at 21:56):

We at least currently do type inference local to a definition, not accounting for its usages elsewhere. So I'd see that as:

add_maker : (Num a -> Num a) -> (Num a -> Num a)
add_maker = |mather|
   |number| mather(number)

multiplier : Int a -> Int a
multiplier = |a| a*10

# Ignore this, it's incorrect
x : Int *
x = add_maker(multiplier)(10)
# Ignore this, it's incorrect

view this post on Zulip Sam Mohr (Feb 07 2025 at 21:59):

Oh, I miscopied, and didn't see the dot after the add_maker usage

view this post on Zulip Eli Dowling (Feb 07 2025 at 21:59):

Oh, so sorry, I forgot the dot! :face_palm:
My point was it's ambiguous If that should work or be a (this function only takes one argument.

view this post on Zulip Eli Dowling (Feb 07 2025 at 22:00):

I just edited it. Typing code on the phone :face_palm::face_palm:

view this post on Zulip jan kili (Feb 07 2025 at 22:01):

Yall should try typing coding like me - browser + trackpad, laying on my belly and elbows, and forgetting to eat lunch... :thought: :pizza: brb gonna stretch and eat

view this post on Zulip Eli Dowling (Feb 07 2025 at 22:01):

Oh wait I'm all mixed up. I suppose it should be multiplier.(add_maker)(10)

view this post on Zulip Sam Mohr (Feb 07 2025 at 22:03):

In that case, the point about the locality of type inference still stands, meaning that multiplier.(add_maker)(10) would desugar to add_maker(multiplier, 10)and typecheck

view this post on Zulip Eli Dowling (Feb 07 2025 at 22:07):

But it would be a type error.
Because add_maker only takes one arg

view this post on Zulip Sam Mohr (Feb 07 2025 at 22:07):

lmao currying make brain hurty

view this post on Zulip Sam Mohr (Feb 07 2025 at 22:08):

I think that's okay

view this post on Zulip Sam Mohr (Feb 07 2025 at 22:08):

It's a cost of a lot of syntax that all uses parens

view this post on Zulip Eli Dowling (Feb 07 2025 at 22:09):

I think, for me, that seems like a pretty serious issue. Not being able to ever make local curried functions seems like something that would come up

view this post on Zulip Eli Dowling (Feb 07 2025 at 22:09):

Anyway I'll make a new thread. It's getting off topic

view this post on Zulip Brendan Hansknecht (Feb 07 2025 at 23:46):

You would need an extra set of parens for it to be a returned function and not a type error:
(multipler.(add_maker))(10)

view this post on Zulip Brendan Hansknecht (Feb 07 2025 at 23:47):

At that point, I would suggest not using .(fn)

view this post on Zulip jan kili (Feb 08 2025 at 00:10):

Snippets from Richard's original proposal:

Here's an example of some real-world Roc code that could make use of this syntax:

reqHeader.value
|> Str.fromUtf8
|> Result.try \s -> s |> Str.split "=" |> List.get 1
|> Result.try Str.toI64

This could become:

Str.fromUtf8(reqHeader.value)
.try(.split("=").get(1))
.try(Str.toI64)

For comparison, here it is again but with the .split("=").get(1) desugared:

Str.fromUtf8(reqHeader.value)
.try(\s -> s.split("=").get(1))
.try(Str.toI64)

view this post on Zulip jan kili (Feb 08 2025 at 00:13):

Richard said:

here are some different ways this could be written based on some ideas in this thread:

.pass_to()

        List.range({ start: At(1), end: Length(total_pages) })
        .map(view_page_link)
        .pass_to(ul, [class("pagination")])

.pt

        List.range({ start: At(1), end: Length(total_pages) })
        .map(view_page_link)
        .pt([class("pagination")])

|>

        List.range({ start: At(1), end: Length(total_pages) })
        .map(view_page_link)
        |> ul([class("pagination")])

.>

        List.range({ start: At(1), end: Length(total_pages) })
        .map(view_page_link)
        .> ul([class("pagination")])

view this post on Zulip jan kili (Feb 08 2025 at 00:15):

Richard said:

Str.from_utf8([byte])
.with_default("???")
.to_u64()
.pass_to(transform_result_somehow)
.with_default(Num.max_u64)

view this post on Zulip jan kili (Feb 08 2025 at 00:16):

(I'm just collecting snippets for us to choose from)

view this post on Zulip jan kili (Feb 08 2025 at 00:18):

Richard said:

dot-dollar

pipes = prev.pipes.$updatePipes("extra argument").appendIfOk(pipe)
pipes = prev.pipes.$Pipes.update("extra argument").appendIfOk(pipe)
pipes =
    prev
    .pipes
    .$updatePipes("extra argument")
    .appendIfOk(pipe)

pipes =
    prev
    .pipes
    .$Pipes.update("extra argument")
    .appendIfOk(pipe)

view this post on Zulip jan kili (Feb 08 2025 at 00:36):

Richard said:

doesn't quite fit on mobile, looks like it's 4 characters too long

Screenshot 2025-01-23 at 10.25.39 AM.png

we could have songs display as s on mobile by having the rest of the word be display:none in css:

Screenshot 2025-01-23 at 11.21.21 AM.png

songs.map(|song| "by ${song.artist}")

view this post on Zulip jan kili (Feb 08 2025 at 00:38):

Richard said:

here's an example of how the homepage could look with a static dispatch example, using all the syntax we've been talking about recently:

roc-lang.org

starred = artists
    .keep_if(.is_starred)
    .map(|artist| "⭐ ${artist.name}")

view this post on Zulip jan kili (Feb 08 2025 at 00:52):

From the tutorial:

List.get(["a", "b", "c"], 100) ?? ""

which could be

["a", "b", "c"].get(100) ?? ""

and (for the .> proposal)

["a", "b", "c"] .> get 100 ?? ""

but since that doesn't look great, that does highlight that newer additions like ?? do seem to be PNC-minded

view this post on Zulip Brendan Hansknecht (Feb 08 2025 at 00:55):

Yeah, ? and ?? are both PNC focused.

view this post on Zulip Brendan Hansknecht (Feb 08 2025 at 00:55):

thus ? for PNC, try for WSA

view this post on Zulip jan kili (Feb 08 2025 at 00:55):

Here's an example of side-by-side formatting we could post in the other thread to present proposals concisely. I'm unsure which one-line & multi-line examples we should pick, but I'm leaning toward us inventing/refactoring more-complete ones (like having Result handling in the one-line example).

:check: :night_sky: Current (old) function application syntax with spaces (WSA)

One-line example

TODO

Multi-line example

main! = |_|
    "./input.txt"
    |> Path.from_str
    |> try Path.read_bytes!
    |> try Foo.from_bytes
    |> transform
    |> try Foo.to_bytes
    |> try Path.write_bytes! (Path.from_str "./output.txt")

    Stdout.line! "🥳 See ./output.txt"

:check: :hot_springs: Current (new) function application syntax with parentheses and commas (PNC)

One-line example

TODO

Multi-line example

main! = |_|
    "./input.txt"
    |> Path.from_str
    |> try Path.read_bytes!
    |> try Foo.from_bytes
    |> transform
    |> try Foo.to_bytes
    |> Path.write_bytes!(Path.from_str("./output.txt"))?

    Stdout.line!("🥳 See ./output.txt")

:sparkles: :hot_springs: Planned static dispatch for PNC

One-line example

TODO

Multi-line example

main! = |_|
    "./input.txt"
    .(Path.from_str)
    .read_bytes!?
    .(Foo.from_bytes)?
    .(transform)
    .to_bytes?
    .(Path.write_bytes!)(Path.from_str("./output.txt"))

    Stdout.line!("🥳 See ./output.txt")

:sparkles: :night_sky: PROPOSAL A for static dispatch for WSA: .>

One-line example

TODO

Multi-line example

TODO

:sparkles: :night_sky: PROPOSAL B for static dispatch for WSA: -->

One-line example

TODO

Multi-line example

TODO

etc...

view this post on Zulip Brendan Hansknecht (Feb 08 2025 at 00:56):

Why two - instead of one in -->?

view this post on Zulip jan kili (Feb 08 2025 at 00:57):

I picked the A/B proposal operators randomly from some of the references above, and Luke suggested that at one point, idk if it was a frontrunner or not

view this post on Zulip jan kili (Feb 08 2025 at 00:57):

JanCVanB said:

...

view this post on Zulip jan kili (Feb 08 2025 at 00:58):

In other words, idk which one we should call "A", I'm just proposing we label them clearly somehow

view this post on Zulip Brendan Hansknecht (Feb 08 2025 at 00:59):

ok

view this post on Zulip jan kili (Feb 08 2025 at 01:02):

I'll fill in the multi-line examples above with the byte reading one Sam and I discussed above, but again I'm open to some other snippet being a better comparison.

view this post on Zulip jan kili (Feb 08 2025 at 01:04):

I'm unsure which syntax highlighting to use for these snippets, since I don't think there's one color scheme that works equally well for both PNC and WSA.

view this post on Zulip Notification Bot (Feb 09 2025 at 20:37):

JanCVanB has marked this topic as resolved.


Last updated: Jun 16 2026 at 16:19 UTC