Stream: ideas

Topic: Static Dispatch and method tear-offs


view this post on Zulip Anthony Bullard (Jan 02 2025 at 14:55):

This idea was first talked about in #show and tell > roc-realworld initial exploration @ 💬 . The basic idea is simple: In Static Dispatch we allow the form of subject.method(arg1, arg2, ...). Sometimes though you'd like to use that method with the subject partially applied to a argument that has a matching signature.

I am suggesting that if we can determine that a) the type of subject is a nominal type, and b) that nominal type has a method with the name method(and obviously the module that the type and method come from), we can desugar to a closure.

# Subject.roc
module [Subject, method]

Subject := {}

new = |{}| Subject.{}

method : Subject, Str -> U64
method = |{}, str| 0 # Implementation not important here
# Other.roc
module []

import Subject

not_important := {} => Result (Str -> U64)  _
     subject = Subject.new({})
     some_effect!.map(subject.method)

The not_important function in Other.roc would be desugared to:

not_important := {} => Result (Str -> U64)  _
     subject = Subject.new({})
     some_effect!.map(|str| subject.method(str))

which would then be desugared by SD to

not_important := {} => Result (Str -> U64)  _
    subject = Subject.new({})
     some_effect!.map(|str| Subject.method(subject, str))

view this post on Zulip Sam Mohr (Jan 02 2025 at 15:07):

Can you give an example in this thread of where you'd want to use this?

view this post on Zulip Anthony Bullard (Jan 02 2025 at 15:08):

I have to run off to work, but if you watch Richard's new video in #show and tell > roc-realworld initial exploration he talks about it

view this post on Zulip Sam Mohr (Jan 02 2025 at 15:08):

I know it's for Ok(router.handle_req!), but I don't know why I want this feature without reading that other thread

view this post on Zulip Anthony Bullard (Jan 02 2025 at 15:08):

Though it's early

view this post on Zulip Anthony Bullard (Jan 02 2025 at 15:09):

Ah, ok. Basically you want it for using a limited form of partial application in a closure position without writing out the closure

view this post on Zulip Anthony Bullard (Jan 02 2025 at 15:10):

It's pure sugar

view this post on Zulip Anthony Bullard (Jan 02 2025 at 15:11):

And, I think people who come to the language because of PNC and SD will find that very intuitive to write as they use it a lot

view this post on Zulip Richard Feldman (Jan 02 2025 at 15:14):

what happens if I have a custom record defined in the same module as a function named foo and then later I add an exposed field to the record named foo? Do all the usage sites silently change to being field access because that takes priority?

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:43):

Note: let subject = Subject -> subject = Subject.new()

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:44):

Do all the usage sites silently change to being field access because that takes priority?

At least if that happened it would immediately fail to compile

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:44):

Hmm... unless the record field loaded happens to be a lambda of the exact same type as the original code

view this post on Zulip Richard Feldman (Jan 02 2025 at 15:54):

yeah

view this post on Zulip Richard Feldman (Jan 02 2025 at 15:55):

arguably it's a bad enough idea to have a method and field of the same name that we could emit a compiler warning if you do that

view this post on Zulip Richard Feldman (Jan 02 2025 at 15:55):

just be like "hey this is error-prone, pick a different name for one of them"

view this post on Zulip Richard Feldman (Jan 02 2025 at 15:55):

I can't think of a situation where it would definitely be a good idea haha

view this post on Zulip Sam Mohr (Jan 02 2025 at 15:57):

In expression contexts, I don't think record.(field!) is currently used for anything. Wouldn't that let us do Ok(router.(handle_req!)) in a way reminiscent of the .pass_to(...) alternative?

view this post on Zulip Richard Feldman (Jan 02 2025 at 16:03):

that's interesting :thinking:

view this post on Zulip Richard Feldman (Jan 02 2025 at 16:03):

certainly the syntax is open right now

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 16:29):

I'm not a fan of that at all. Adds weirdness and friction where it doesn't seem needed

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 16:31):

Also, I still really hope we use .(func)(args) instead of .pass_to(func, args)

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 16:31):

So really don't want to steal the syntax currently

view this post on Zulip Sam Mohr (Jan 02 2025 at 16:31):

That's what this pushes for

view this post on Zulip Sam Mohr (Jan 02 2025 at 16:32):

Well, it's different, I suppose

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 16:33):

Yeah, those would be totally different meanings

view this post on Zulip Anthony Bullard (Jan 02 2025 at 16:34):

Brendan Hansknecht said:

Note: let subject = Subject -> subject = Subject.new()

I've been writing too much Rust :-)

view this post on Zulip Anthony Bullard (Jan 02 2025 at 16:36):

I think Sam is on to something actually....

view this post on Zulip Anthony Bullard (Jan 02 2025 at 16:37):

The desugaring is the same (at least textually)

Ok(router.(handle_req!))

to

Ok(|req| router.(handle_req!)(req))

view this post on Zulip Anthony Bullard (Jan 02 2025 at 16:37):

Oh no, it would have to be

view this post on Zulip Anthony Bullard (Jan 02 2025 at 16:38):

Ok((router.handle_req!))

that desugars to

Ok(|req| (router.handle_req!)(req))

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 16:38):

Yeah, it is definitely different. One is using static dispatch, the other is using an imported method.

view this post on Zulip Anthony Bullard (Jan 02 2025 at 16:40):

So

subject.method() # static dispatch
(subject.method)() # record closure field apply
subject.(func)() # pass to local function

view this post on Zulip Anthony Bullard (Jan 02 2025 at 16:41):

Ok(subject.method) # static dispatch
Ok((subject.method)) # record closure field apply
Ok(subject.(func)) # pass to local function - which is actual generic partial application

view this post on Zulip Anthony Bullard (Jan 02 2025 at 16:42):

:dizzy:

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 16:43):

Looks logical

view this post on Zulip Richard Feldman (Jan 02 2025 at 16:43):

in general, (a) evaluates to a

view this post on Zulip Richard Feldman (Jan 02 2025 at 16:43):

I don't think we can make (subject.method) evaluate to something different than subject.method

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 16:44):

I definitely like the first half:

subject.method() # static dispatch
(subject.method)() # record closure field apply
subject.(func)() # pass to local function

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 16:45):

I feel like I need to look more at the real world repo before I have an option on all of the partial application cases

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 16:45):

Feels convenient, but not sure how necessary

view this post on Zulip Anthony Bullard (Jan 02 2025 at 16:50):

Tearoffs are never necessary, but I think with method syntax it's very helpful

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 16:51):

Sorry, necessary was the wrong word. How much I will feel the helpfulness outways the cost of adding another special cases.

view this post on Zulip Anthony Bullard (Jan 02 2025 at 16:52):

For sure


Last updated: Jun 16 2026 at 16:19 UTC