Stream: ideas

Topic: module interface API syntax


view this post on Zulip Luke Boswell (Jan 15 2025 at 02:42):

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,
}

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

Personally I love it. But I know that might be controversial

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

As long as the annotations are optional of course

view this post on Zulip Luke Boswell (Jan 15 2025 at 02:47):

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],
}

view this post on Zulip Luke Boswell (Jan 15 2025 at 02:48):

So the pattern for the API is basically <thing> <intputs> -> <outputs>

view this post on Zulip Luke Boswell (Jan 15 2025 at 02:50):

again, with the types being optional

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

Yep and the inputs maybe only existing in some of these

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

And put a <name> in between thing and inputs and I love it

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

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

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

I wonder if editor inlay hints could show those types when you're looking at the module export list at the top :thinking:

view this post on Zulip Luke Boswell (Jan 15 2025 at 02:54):

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!,
}

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

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

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

But regardless of the annotation part, I like using {} here

view this post on Zulip Luke Boswell (Jan 15 2025 at 02:56):

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!
}

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

It’s a record, not a list

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

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

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

record makes sense if they're annotated, but { len } is shorthand for { len: len } everywhere else

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

Yeah, the exporter member len is defined as the module member len

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

Field punning

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

You could even allow a rename for export if you wanted

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

reasonable point!

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

Chakra did this exactly

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

So maybe I’m biased

view this post on Zulip Anthony Bullard (Jan 15 2025 at 03:00):

But it only had modules

view this post on Zulip Luke Boswell (Jan 15 2025 at 03:02):

Using a keyword to export things to the host...

platform { ... } -> { ... }

import random.Random
import json.Json
export initForHost!
export renderForHost!

initForHost! : ...

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

Seems reasonable and keeps the record semantics consistent

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

And you would only see this syntax in one file per platform

view this post on Zulip Derin Eryilmaz (Jan 15 2025 at 12:58):

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