While staring at different syntax ideas for the static dispatch discussion, I had this idea and thought I'd put it out there. I think we've talked something like this before around about the module params discussion, but I'm not sure.
What if instead of declaring a module like so;
module [
Json,
utf8,
utf8_with,
encode_as_null_option,
]
We declare the module and include the types of it's exported functions, like we are declaring it's external "interface" or API. The {} -> below is optional, and may not be needed if we removed module params.
module {} -> {
Json,
utf8 : Json,
utf8_with : {
field_name_mapping ?? FieldNameMapping,
skip_missing_properties ?? Bool,
null_decode_as_empty ?? Bool,
empty_encode_as_null ?? EncodeAsNull,
} -> Json,
encode_as_null_option: {
list ?? Bool,
tuple ?? Bool,
record ?? Bool,
} -> EncodeAsNull,
}
Instead of having the type annotations on the definitions, we instead could instead include them in the header (or neither or both I guess). I like how you could look at a header an see it's API for that module just from the types.
And then the discussion for dispatch on return types could then look like this instead... which looks like normal syntax compared to [, ].
Encoder a := module {
encode : a -> List U8,
}
Personally I love it. But I know that might be controversial
As long as the annotations are optional of course
May as well throw the others in there in the same style..
package {
random: "https://...tar.br",
} -> {
Json,
Option,
OptionOrNull,
}
app {
cli: platform "../../basic-cli/platform/main.roc",
json: "../package/main.roc", # use release URL (ends in tar.br) for local example, see github.com/lukewilliamboswell/roc-json/releases
} -> {
main! : List Arg -> Result {} [Exit I32 Str],
}
So the pattern for the API is basically <thing> <intputs> -> <outputs>
again, with the types being optional
Yep and the inputs maybe only existing in some of these
And put a <name> in between thing and inputs and I love it
I think the downside would be that when I'm looking at the body of the function, it's nice to see the type right above it, and when I'm doing things like adding or removing an arg, it's nice to be able to change both in one place without jumping around
I wonder if editor inlay hints could show those types when you're looking at the module export list at the top :thinking:
platform {
Model
init! : {} => Result Model []_,
render! : Model, RocRay.PlatformState => Result Model []_,
} -> {
# these modules "exposed" for the API
RocRay,
Camera,
Draw,
Font,
Keys,
Mouse,
Music,
Network,
RenderTexture,
Sound,
Texture,
Time,
# these are the entrypoints "exposed" for the host
initForHost!,
renderForHost!,
}
Richard Feldman said:
I wonder if editor inlay hints could show those types when you're looking at the module export list at the top :thinking:
In an editor, sure. But not in git. And in an editor you could have a code action to sync the signature
But regardless of the annotation part, I like using {} here
app {
rr: platform "../platform/main.roc",
rand: "https://github.com/lukewilliamboswell/roc-random/releases/download/0.3.0/hPlOciYUhWMU7BefqNzL89g84-30fTE6l2_6Y3cxIcE.tar.br",
time: "https://github.com/imclerran/roc-isodate/releases/download/v0.5.0/ptg0ElRLlIqsxMDZTTvQHgUSkNrUSymQaGwTfv0UEmk.tar.br",
} -> {
Model,
init!,
render!
}
It’s a record, not a list
Luke Boswell said:
platform { Model init! : {} => Result Model []_, render! : Model, RocRay.PlatformState => Result Model []_, } -> { # these modules "exposed" for the API RocRay, Camera, Draw, Font, Keys, Mouse, Music, Network, RenderTexture, Sound, Texture, Time, # these are the entrypoints "exposed" for the host initForHost!, renderForHost!, }
I wonder if expose a member for the host could be a separate statement type in the header like imports
record makes sense if they're annotated, but { len } is shorthand for { len: len } everywhere else
Yeah, the exporter member len is defined as the module member len
Field punning
You could even allow a rename for export if you wanted
reasonable point!
Chakra did this exactly
So maybe I’m biased
But it only had modules
Using a keyword to export things to the host...
platform { ... } -> { ... }
import random.Random
import json.Json
export initForHost!
export renderForHost!
initForHost! : ...
Seems reasonable and keeps the record semantics consistent
And you would only see this syntax in one file per platform
I'm a big fan of this syntax since exports all have to be written in one place anyway. And also this feels very structural-typed, which matches with (non opaque) struct and enum behavior
Last updated: Jun 16 2026 at 16:19 UTC