@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 ?
This is now a messy noisy desktop for collaboration. Please don't link to any message in here.
#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
Cool, I'll pick that up then
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
In that thread,
[1,2,3]->map \n -> n + -1
aSet->insert 42
23->to_u64
-->: or :: or something else||> surrounded by whitespaceallItems
|> _.mapTry \item -> validate item
|> _.len
|> "just working" to avoid conflict with field accessinitialData
|> .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_fieldis a big improvement over this?
initial_data .map(.inner_data) .aggregate() .first_field .second_field
and expressed preference for the latter
When organizing side-by-sides, I wonder how we should approach complications like:
try vs ? (we could commit to try for WSAland or explore ?+WSA options)????I don't think there's much want for ? in WSA, I'd just focus on try for WSA and ? for PNC
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)
I'm curious what that multiline+effectful (but not resultful?) example looks like with some of the proposals above:
->/-->/:/:: doesn't work with multiline?||>: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"
|> "just working":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...
Maybe it would be best to provide 2 examples (one single-line, one multi-line) for each WSA SD syntax proposal?
Should we make both medium-high complexity? For example, having the single-line include some kind of Result handling? and nested function calls?
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.
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??"
because I've heard that mentioned as a side effect of WSA leaving
Yes, |> is gone is WSA leaves
Because there's no need for it in PNC land
We should probably move this to another topic, but can't multiline PNC not function today without it?
I'm worried that could give us a few months before SD lands where 150-character lines are expected lol
It causes precedence problems when you have chains of whitespace and method calls interwoven that are visually confusing
I think the item.(local_func)(args) syntax should avoid the prevalence of long method chains
Does that land way before SD?
It's purely sugar for local_func(item, args) so it definitely could
The timeline is more of a @Anthony Bullard @Joshua Warner question
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.
If there are no args, you don't need the second ()
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")
Correction to above: This isn't possible "today" because .(local) needs to land first.
Today without :pizza: you need to invert the call order, right?
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")
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.
But I hear you that that's doable after SD lands. Today :pizza: is still needed for multilining like that without inverting call order.
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 .$
I don't think .$ is planned for usage anywhere
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")
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")
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)
When showing WSA+SD though, we should show that forecasted PNC+SD as the comparison!
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"
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)
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
Oh, I miscopied, and didn't see the dot after the add_maker usage
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.
I just edited it. Typing code on the phone :face_palm::face_palm:
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
Oh wait I'm all mixed up. I suppose it should be multiplier.(add_maker)(10)
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
But it would be a type error.
Because add_maker only takes one arg
lmao currying make brain hurty
I think that's okay
It's a cost of a lot of syntax that all uses parens
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
Anyway I'll make a new thread. It's getting off topic
You would need an extra set of parens for it to be a returned function and not a type error:
(multipler.(add_maker))(10)
At that point, I would suggest not using .(fn)
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.toI64This 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)
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")])
.ptList.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")])
Richard said:
Str.from_utf8([byte]) .with_default("???") .to_u64() .pass_to(transform_result_somehow) .with_default(Num.max_u64)
(I'm just collecting snippets for us to choose from)
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)
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
songsdisplay asson mobile by having the rest of the word bedisplay:nonein css:
songs.map(|song| "by ${song.artist}")
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:
starred = artists
.keep_if(.is_starred)
.map(|artist| "⭐ ${artist.name}")
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
Yeah, ? and ?? are both PNC focused.
thus ? for PNC, try for WSA
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).
TODO
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"
TODO
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")
TODO
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")
.>TODO
TODO
-->TODO
TODO
Why two - instead of one in -->?
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
JanCVanB said:
...
- and Luke countered with extending it to
-->
...
In other words, idk which one we should call "A", I'm just proposing we label them clearly somehow
ok
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.
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.
JanCVanB has marked this topic as resolved.
Last updated: Jun 16 2026 at 16:19 UTC