Stream: ideas

Topic: ✔ Static dispatch without parens and commas


view this post on Zulip Anthony Bullard (Dec 18 2024 at 02:36):

I think that the current syntax of Roc is beautiful. While I’ve been known to say that the move to PNC envisioned as necessary to the Static Dispatch proposal is probably a good move for adoption, it does threaten to lose a lot of that beauty.

So I’d like to use this thread to explore alternative syntactic constructs that could be used for the static dispatch feature within the current syntax.

I’ll start by suggesting an obvious choice, that has deep memory in programming and doesn’t not fall far outside the existing syntactic space: ->.

This would be a new infix operator that must have NO white space between itself and its operands.

[1,2,3]->map \n -> n + -1
aSet->insert 42
23->to_u64

It would apply the left operand to the right operand as its first argument, additional arguments would follow. If the function is unary, the application would be complete.

view this post on Zulip Brendan Hansknecht (Dec 18 2024 at 03:14):

And it has to be a static dispatch method.

view this post on Zulip Brendan Hansknecht (Dec 18 2024 at 03:14):

So basically a different form of pipe

view this post on Zulip Luke Boswell (Dec 18 2024 at 03:22):

Riffing of the idea a little, I assume the NO white space is so it's discernable from other operators, why not just use double dash, that renders nicely in my editors.

[1,2,3] --> map \n -> n + -1
aSet --> insert 42
23 --> to_u64

Screenshot 2024-12-18 at 14.21.38.png

view this post on Zulip Luke Boswell (Dec 18 2024 at 03:24):

Brendan's comment made me think of the difference between an en and em dash from english. You use an em (longer) dash to break a sentence.

view this post on Zulip Anthony Bullard (Dec 18 2024 at 03:27):

Brendan Hansknecht said:

So basically a different form of pipe

This is EXACTLY my thought, a “static dispatch pipe”

view this post on Zulip Anthony Bullard (Dec 18 2024 at 03:28):

Luke Boswell said:

Riffing of the idea a little, I assume the NO white space is so it's discernable from other operators, why not just use double dash, that renders nicely in my editors.

[1,2,3] --> map \n -> n + -1
aSet --> insert 42
23 --> to_u64

Screenshot 2024-12-18 at 14.21.38.png

I’m not sold on any one specific operator, : or :: could be used as well

view this post on Zulip Brendan Hansknecht (Dec 18 2024 at 03:43):

Technically could even use a normal pipe and add a static dispatch sigil to the called method. That said, attaching to the core object probably makes it clearer

view this post on Zulip Brendan Hansknecht (Dec 18 2024 at 03:44):

Also, I agree with the general feeling that these changes lose a lot of the feel and "soul" of current roc

view this post on Zulip Luke Boswell (Dec 18 2024 at 03:44):

Used an example from the static dispatch proposal
https://gist.github.com/lukewilliamboswell/546c391fc56bf2c825760d2d7fd9b3a4

view this post on Zulip Romain Lepert (Dec 18 2024 at 09:36):

static dispatch pipe could use double pipe

[1,2,3] ||> map \n -> n + -1
aSet ||> insert 42
23 ||> to_u64

view this post on Zulip Sam Mohr (Dec 18 2024 at 09:41):

I know that Rich is not a fan of operators over two characters, which this proposal has strongly leaned into. Roc doesn't currently have any, which helps with terseness.

view this post on Zulip Sam Mohr (Dec 18 2024 at 09:42):

These suggestions help preserve the aesthetic of current Roc, but are missing the big benefit of . being short and easy to type, as well as intuitive for people coming from other languages.

view this post on Zulip Sam Mohr (Dec 18 2024 at 09:42):

Maybe we can try to consider operators that are two or fewer characters and are easy to type?

view this post on Zulip Norbert Hajagos (Dec 18 2024 at 10:55):

I believe we've went through a lot of operators in one of the static dispatch proposal discussions.
I don't remember -> being mentioned. Besides ., -> and :: are the only operators I could see as a solution. To me:: would be confusing, since I think of OOP static methods on a class. But -> would work. It doesn't have spaces before it, so it is a 2 character operator. Not as easy as . , but would still satisfy the "lsp completion friendly" characteristic.
A beginner would confuse a 1 arg method for a field access tho, a la C / PHP.

# is this a costly method on a singly linked list,
# or a dirt cheap field access of a doubly linked list?
linkedList->last

I don't know how far the PnC syntax implementation is, but this would be a nice first iteration of the syntax that enables static dispatch. If we like it in anger, the last one. Personally I would be happy with both this and PnC .

(It feels weird to choose PHP syntax-hl for Roc :grinning_face_with_smiling_eyes: )

view this post on Zulip Sam Mohr (Dec 18 2024 at 12:07):

Since this aims to generally preserve whitespace calling/avoid parens while getting the benefit of type inference, maybe module inference would fill your wants?

allItems
|> _.mapTry \item -> validate item
|> _.len

I don't think just |> .len works because that's a field accessor.

view this post on Zulip Anthony Bullard (Dec 18 2024 at 12:53):

I personally don’t understand stand-alone field accessors

view this post on Zulip Sam Mohr (Dec 18 2024 at 13:15):

They're good for data drilling in pipelines, or in mapping operations:

initialData
|> List.map .innerData
|> List.aggregate
|> .firstField
|> .secondField

view this post on Zulip Derin Eryilmaz (Dec 18 2024 at 14:54):

I'm a big fan of this idea because you can also call functions in the normal style if you want:

_.add 1 2

view this post on Zulip Anthony Bullard (Dec 18 2024 at 14:58):

(allItems->mapTry \item ->
    validate item)->len

Is what I was thinking.

view this post on Zulip Anthony Bullard (Dec 18 2024 at 14:58):

Parens only needed for disambiguation. Would not be needed for literals or identifiers

view this post on Zulip Anthony Bullard (Dec 18 2024 at 14:59):

(allItems->mapTry |item| validate item)->len

With || lambas

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:01):

initialData
|> List.map .innerData
|> List.aggregate
|> .firstField
|> .secondField

Would become

(((initialData->map innerData)->aggregate).firstField).secondField

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:02):

I don't know about the parens here

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:02):

I think it could be this

(initialData->map innerData)->aggregate.firstField.secondField

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:03):

Or this

(initialData->map innerData)->aggregate
|> .firstField
|> .secondField

view this post on Zulip Derin Eryilmaz (Dec 18 2024 at 15:06):

once you start chaining .a.b.c it starts looking like modules again

view this post on Zulip Derin Eryilmaz (Dec 18 2024 at 15:07):

Personally I like the |> _.function

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:08):

If we could have |> itself be "static dispatch aware" before a `.someMember name that would be awesome

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:08):

And then there is NO NEW SYNTAX

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:09):

It's just a "record field accessor" becomes that for a record, and if that type has no such fields (or is not a record) we fall back to static dispatch. Does that sound too complicated to implement @Sam Mohr ?

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:11):

In that case Sam's example becomes

initialData
|> .map .innerData
|> .aggregate
|> .firstField
|> .secondField

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:12):

Maybe all the "magic" is happening in the accessor? So we now have static dispatch and the beginning of "terse lambas"

view this post on Zulip Kasper Møller Andersen (Dec 18 2024 at 15:12):

That would also distinguish local functions which don’t need the dot in front presumably

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:12):

Yep

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:13):

The "record field accessor" becomes instead the "type member accessor"

view this post on Zulip Derin Eryilmaz (Dec 18 2024 at 15:15):

Would there not be a conflict there?

view this post on Zulip Derin Eryilmaz (Dec 18 2024 at 15:16):

Say you write a function \bar -> .foo bar, is bar a {foo: a} or is it an opaque type that has a function called foo in the module?

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:16):

Only if a record has both a field and a function in it's origin module with the same name

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:17):

Good point, You'd have to treat a record having a field as it having the accessor for foo -> a

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:18):

Which opens a can of worms about did we just allow structural abilities to rely on fields? Or did we just find a convenient workaround to people creating getters?

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:19):

The type of that functions is sort of like "a -> b where a implements foo : a -> b"

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:20):

Which conveniently enough is the same as just .foo itself

view this post on Zulip Derin Eryilmaz (Dec 18 2024 at 15:22):

But then you sort of lose the readability of having record types with {}

view this post on Zulip Eli Dowling (Dec 18 2024 at 15:23):

I quite like this idea, I want to quickly put out the suggestion that we not use the pipe operator for static dispatch and instead use some other operator.
Given static dispatch inherently implies the pipe.

Eg:
.> For static dispatch
|> for pipe

initialData
.> map .>innerData
.> aggregate
|> .firstField
|> .secondField

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:24):

I have to go to work, but I can't wait for Anton, Brendan, Luke, Sam, and Richard (maybe even Ayaz) to tell me that this won't work. But I think it's consistent with an interpretation of row polymorphism

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:24):

Eli Dowling said:

I quite like this idea, I want to quickly put out the suggestion that we not use the pipe operator for static dispatch and instead use some other operator.
Given static dispatch inherently implies the pipe.

Eg:
.> For static dispatch
|> for pipe

initialData
.> map .>innerData
.> aggregate
|> .firstField
|> .secondField

The idea is that the pipe IS NOT doing the static dispatch the accessor itself is

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:24):

But I really gotta go :-)

view this post on Zulip Eli Dowling (Dec 18 2024 at 15:26):

Sure but now we are typing |> . Every function call... A pretty hard no from me as far as economics.

view this post on Zulip Eli Dowling (Dec 18 2024 at 15:27):

ergonomics* (though economy works too :sweat_smile:)

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:27):

This isn’t a long term solution maybe and it’s not every function call

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:28):

Only to those functions implemented in the types own originating module

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:28):

You can still do Whatever.function arg

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:28):

Or arg |> .function

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:29):

And maybe the pipe isn’t necessary from a literal or identifier

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:30):

arg.function should be unambiguous

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:30):

Except it’s unclear now if it is field access or a function call

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:32):

Does that matter? The type solver should take to such a function as Derin gave above a record with a foo field or a static dispatch function foo that is unary and returns the same type

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:32):

To me this is about “how can we test static dispatch without PNC?”

view this post on Zulip Anthony Bullard (Dec 18 2024 at 15:34):

And I’ve said a lot - but I know this might not work or be a good idea. Just trying to keep the conversation going :wink:

view this post on Zulip Derin Eryilmaz (Dec 18 2024 at 15:47):

Anthony Bullard said:

Or arg |> .function

or just .function arg

view this post on Zulip Derin Eryilmaz (Dec 18 2024 at 15:48):

but i still feel like there are too many ambiguties with this syntax. maybe it should be _.function for static dispatch and .function for record property access

view this post on Zulip Brendan Hansknecht (Dec 18 2024 at 17:45):

One note is that .> something is more chainable (with good auto complete) than something like |> _.something

And I don't think that -> alone is chainable at all.

view this post on Zulip Brendan Hansknecht (Dec 18 2024 at 17:49):

One of the biggest benefits of .something() is how easily chainable and auto complete friendly it is. Not to mention familiar.

view this post on Zulip Brendan Hansknecht (Dec 18 2024 at 17:50):

I feel like I really need to convert a medium sized app over to parens and commas. I think the apps I write today don't use enough opaque types to take advantage of it. That's actually my biggest concern with static dispatch. It only works for opaque types and really promotes adding way more of them.

view this post on Zulip Anthony Bullard (Dec 18 2024 at 17:51):

Yes and opaque types are basically nominal typing

view this post on Zulip Sam Mohr (Dec 18 2024 at 17:51):

Anthony Bullard said:

I have to go to work, but I can't wait for Anton, Brendan, Luke, Sam, and Richard (maybe even Ayaz) to tell me that this won't work. But I think it's consistent with an interpretation of row polymorphism

This is now the CPR scene from the Abyss. I don't expect success, but I am here to help you feel like you did what you could. And if you find life in a dying body, that's a great thing

view this post on Zulip Sam Mohr (Dec 18 2024 at 18:05):

Anthony Bullard said:

It's just a "record field accessor" becomes that for a record, and if that type has no such fields (or is not a record) we fall back to static dispatch. Does that sound too complicated to implement Sam Mohr ?

Might be a little weird to do, but doable

view this post on Zulip Richard Feldman (Dec 18 2024 at 19:01):

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

view this post on Zulip Anthony Bullard (Dec 18 2024 at 19:02):

Not a big improvement - just a way to eat our static dispatch cake and have our PNC-free Roc too

view this post on Zulip Richard Feldman (Dec 18 2024 at 19:03):

to me, neither of these feels like today's Roc, but if I had to pick one or the other I'd pick the second one

view this post on Zulip Anthony Bullard (Dec 18 2024 at 19:03):

And if SD doesn’t work it’s a small bit to abandon ship on

view this post on Zulip Richard Feldman (Dec 18 2024 at 19:04):

I mean believe me, I have plenty of affection for and fond memories of the current syntax, but I think as soon as you take away the module qualification it unavoidably feels like something different to me

view this post on Zulip Richard Feldman (Dec 18 2024 at 19:04):

and at that point I'd rather optimize for what feels like it's the best overall design, not something that's a compromise of the best design and the status quo I'm nostalgic for :big_smile:

view this post on Zulip Anthony Bullard (Dec 18 2024 at 19:05):

That’s totally fair

view this post on Zulip Derin Eryilmaz (Dec 18 2024 at 20:17):

for that code example, it's .first_field.second_field right? Not .first_field().second_field()

view this post on Zulip Richard Feldman (Dec 18 2024 at 20:52):

oops yeah, fixed!

view this post on Zulip Anthony Bullard (Dec 18 2024 at 22:39):

Ok, I hope this conversation was helpful someway. I think PNC will carry the day. I will shed one single tear, and get ready for the new world with open eyes and an open heart.

Oh, and I'll mark this as resolved.

view this post on Zulip Notification Bot (Dec 18 2024 at 22:39):

Anthony Bullard has marked this topic as resolved.

view this post on Zulip Brendan Hansknecht (Dec 18 2024 at 23:03):

I think after we try PNC, we'll truly see the consensus.

view this post on Zulip Brendan Hansknecht (Dec 18 2024 at 23:03):

I think it will take use in practice to really get the feel.

view this post on Zulip Anthony Bullard (Dec 18 2024 at 23:04):

Yerah, that's why I am trying out the syntax in some non-trivial applications (my AOC 2024 repo)


Last updated: Jun 16 2026 at 16:19 UTC