Stream: announcements

Topic: New module headers and imports


view this post on Zulip Agus Zubiaga (May 03 2024 at 14:48):

New module headers and imports

@everyone our latest nightly comes with some significant changes to our module system including new syntax and features.

New headers

Apps

App headers have been simplified and now only specify the values/types provided to the platform (such as main), and the packages they depend on.

app [main] {
    pf: platform "https://…",
    json: "https://…/json/…",
}

Note: The platform package entry now needs to be marked explicitly.

Modules (formerly interfaces)

Interfaces have been renamed to "modules" and their headers only specify the values and types they expose. Their name is now only derived from the file path, so you no longer have to keep them in sync.

module [Request, Response, req]

Packages

Finally, package headers have also been streamlined to specify what modules they expose and which other packages they depend on.

package [ParserCore, ParserCsv, ParserStr] {
    json: "https://…/json/…",
    unicode: "https://…/unicode/…",
}

Platform

For now, platform and hosted headers remain the same, but there are plans to update those as we make more changes to the module system (e.g. Task as a builtin).

New imports

Instead of specifying the imported modules in the header, we'll now use the import keyword.

app [main] { pf: platform "https://…/basic-cli" }

import pf.Stdout

main =
    Stdout.line! "Hello world"

Exposing

If you want to bring a name into your scope, you can use the exposing keyword:

import pf.Stdout exposing [line]

We now handle shadowing correctly, so you'll get a helpful error message if the name is already in scope.

Aliasing

You can now bring the module into scope under a different name using the as keyword:

import json.Core as Json

You can use this to resolve conflicts if two packages were to use the same name for a module.

The name alias must not conflict with any other names in scope.

Ingested files

Ingested files are still supported and look like this:

import "friends.txt" as friends : Str
import "logo.png" as logo : List U8

Note: The annotation is currently mandatory, but we'll make that optional soon.

Smaller scopes

You can now introduce an import or ingested file at a deeper scope than the top one.

query =
    import pg.Sql exposing [from, select, where, like, str]

    users <- from Db.users

    select {
        name: <- users.name,
    }
    |> where (users.name |> like (str "John%"))

This is useful if you don't want to pollute your scope with e.g. DSL functions, or test helpers (they can also appear inside expect). In the future, this will also allow us to provide non-static module params when those are implemented.

Importing in the REPL

You can now import local modules and ingest files in the REPL!

Watch a short demo in the PR.

General improvements

In reimplementing parts of the module system, many bugs have been fixed and error messages have been improved:

Not everything is fixed, though. There are still old bugs related to packages and other improvements coming later.

Upgrading

The old syntax for headers and imports should still parse and work correctly, so you don't need to rush to update everything. Your app can still use packages and platforms with the old syntax.

That said, running roc format should upgrade all your code automatically!

CleanShot-2024-05-01-at-15.51.09.mp4

view this post on Zulip Anton (May 03 2024 at 16:06):

The newest nightlies come with some warnings if your project uses roc-json. Luke's on vacation so I made a quick patch roc-json release from my updated fork.

view this post on Zulip Agus Zubiaga (May 03 2024 at 16:10):

Ah, yes. I made a PR but it looks like it hasn’t landed yet. I also made an issue to prevent warnings from (remote) package from showing up when you build your app.

view this post on Zulip Isaac Van Doren (May 03 2024 at 16:53):

This is awesome!! Thanks for all your work Agus! :star:

view this post on Zulip Luke Boswell (May 03 2024 at 17:18):

Updated json release https://github.com/lukewilliamboswell/roc-json/releases/tag/0.8.0

view this post on Zulip Luke Boswell (May 03 2024 at 17:19):

It was just waiting on the new nightly

view this post on Zulip Isaac Van Doren (May 04 2024 at 21:42):

Using roc format to update to the new syntax is so slick :star_struck:

view this post on Zulip Kevin Gillette (May 09 2024 at 03:38):

Is the distinction between packages and modules that all modules (perhaps aside from the builtins) must live in a package?

view this post on Zulip Kevin Gillette (May 09 2024 at 03:40):

Can a module be used without an import as long as its containing package is in the header? i.e. is pf.Stdout.line allowed in expressions?

view this post on Zulip Kevin Gillette (May 09 2024 at 03:57):

Agus Zubiaga said:

You can now introduce an import or ingested file at a deeper scope than the top one.

query =
    import pg.Sql exposing [from, select, where, like, str]
    # ...

In other languages this might be discouraged due to it becoming harder for readers to reason about the dependencies/capabilities solely by looking at the top of a file.

I'm guessing this is less of an issue for Roc because utilizing external code involves two layers (packages and imports), and the outer package layer is in the header anyways?

view this post on Zulip Brendan Hansknecht (May 09 2024 at 07:45):

Packages are made up of modules. Packages can be imported remotely and from any local directory.

Modules can only be imported locally/from a subdirectory.

view this post on Zulip Agus Zubiaga (May 09 2024 at 11:33):

We just discovered that the formatter in the language server is not properly upgrading imports to the new syntax like the CLI formatter is. This PR will fix that, but in the meantime, if you're working on an app with old syntax, you should first run roc format through CLI to properly upgrade. The LS formatter should work fine after that.

view this post on Zulip JRI98 (May 09 2024 at 14:26):

Love the new syntax. Just have a quick question: how can I have a module that depends on a package (like roc-json for example)?

view this post on Zulip Agus Zubiaga (May 09 2024 at 14:29):

You have to add it to your app's header first:

app [main] {
    pf: platform "...",
    json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.9.0/JI4BuuOuWnD1R3Xcx-F8VrWdj-LM_FfDRB00ekYjIIQ.tar.br"
}

import MyModule

and then you can use it in MyModule.roc like this:

module [things, you, expose]

import json.Json # We use the `json` package shorthand that we defined in the `app`

view this post on Zulip Agus Zubiaga (May 09 2024 at 14:32):

Currently, if you want to run roc check/roc test, you have to run it on the app file and it will check everything it imports. If you ran it on the module file, we'll fail to resolve the "json" package shorthand.

In the future , we plan to fix this by searching for the app automatically, and allow you to specify it through a CLI argument too #6538.

view this post on Zulip JRI98 (May 09 2024 at 14:45):

Following your example I have this:

main.roc

app [main] {
    pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.10.0/vNe6s9hWzoTZtFmNkvEICPErI9ptji_ySjicO6CkucY.tar.br",
    json: "https://github.com/lukewilliamboswell/roc-json/releases/download/0.9.0/JI4BuuOuWnD1R3Xcx-F8VrWdj-LM_FfDRB00ekYjIIQ.tar.br",
}

import MyModule
import pf.Stdout
import pf.Stderr

main =
    when Str.fromUtf8 (Encode.toBytes {} MyModule.jsonCoder) is
        Ok o ->
            Stdout.line! o

        Err err ->
            Stderr.line! (Inspect.toStr err)

MyModule.roc

module [jsonCoder]

import json.Json

jsonCoder = Json.utf8With { fieldNameMapping: SnakeCase, skipMissingProperties: Bool.true }

With this I have two issues:

This expression has a type that does not implement the abilities it's expected to:

11│      when Str.fromUtf8 (Encode.toBytes {} MyModule.jsonCoder) is
                                              ^^^^^^^^^^^^^^^^^^

The type Json does not fully implement the ability EncoderFormatting.

view this post on Zulip Agus Zubiaga (May 09 2024 at 15:03):

The roc_ls issue is known and is caused by the issue I described where the compiler does not know where your app is. It's a bug we've had for a while. I'm planning to fix it, but there are a few other things I'm looking into first.

view this post on Zulip Agus Zubiaga (May 09 2024 at 15:12):

As per the abilities issue, I hadn't seen this one yet. @JRI98 Can you create an issue for that?

view this post on Zulip JRI98 (May 09 2024 at 15:17):

Opened https://github.com/roc-lang/roc/issues/6740

view this post on Zulip Agus Zubiaga (May 09 2024 at 15:18):

thank you!

view this post on Zulip Oskar Hahn (May 16 2024 at 10:25):

Would it be possible, to alias an exposed symbol? For example

import Sql.Session exposing [parse as parseSession]

Last updated: Jul 26 2025 at 12:14 UTC