Stream: ideas

Topic: package shorthands when compiling interface modules


view this post on Zulip Luke Boswell (Feb 18 2024 at 05:50):

Bit of a nit, but the following issue is a bit of a pain now that I have refactored into separate modules with every file having this error.

Just wondering if this is something we could easily fix?

Screenshot-2024-02-18-at-16.46.59.png

view this post on Zulip Richard Feldman (Feb 18 2024 at 13:18):

I'm not sure if we should call it a bug; the problem is that we don't currently have a way to specify what the main module is

view this post on Zulip Richard Feldman (Feb 18 2024 at 13:19):

like if all roc receives is a path to an interface module, then it doesn't know where the main module can be found

view this post on Zulip Richard Feldman (Feb 18 2024 at 13:19):

it can guess it's named main.roc but currently that's not a feature

view this post on Zulip Richard Feldman (Feb 18 2024 at 13:48):

there's a design question there.

We could for example have interface modules specify the path to their main module, but then it becomes impossible to have multiple main modules share the same interface modules. Maybe that's fine?

Also we could have it be an optional thing for interface modules to specify, with a default of (for example) ./main.roc or perhaps ../main.roc

view this post on Zulip Richard Feldman (Feb 18 2024 at 13:51):

we could also add a cli flag like --main which is only used when compiling interface modules, and which defaults to something

view this post on Zulip Luke Boswell (Feb 18 2024 at 19:12):

I assumed there is a design with module
params that resolves this issue.

view this post on Zulip Richard Feldman (Feb 18 2024 at 19:16):

hm, I hadn't thought about that :thinking:

view this post on Zulip Richard Feldman (Feb 18 2024 at 19:16):

what might that look like?

view this post on Zulip Luke Boswell (Feb 18 2024 at 19:58):

Sorry, I just meant I thought this would have already been thought of. But it sounds like something that needs exploration.

view this post on Zulip Luke Boswell (Feb 18 2024 at 20:07):

Can we move this conversation to an ideas channel?

What about specifying the path to dependencies like this? It could take a URL or a local path just like the app.

interface Pages.Home
    exposes [view]
    imports [
        html.Html,
        html.Attribute,
        Model.{Session},
        Layout.{layout},
        NavLinks,
    ]

roc check ./Pages/Home.roc html="https://github.com/Hasnep/roc-html/releases/download/v0.2.1/gvFCxQTb3ytGwm7RQ87BVDMHzo7MNIM2uqY4GBDSP7M.tar.br"

view this post on Zulip Brian Carroll (Feb 18 2024 at 21:45):

Why does an interface module need a main module in order to compile?

view this post on Zulip Richard Feldman (Feb 18 2024 at 21:48):

it needs to know what the package shorthands mean

view this post on Zulip Richard Feldman (Feb 18 2024 at 21:48):

like pf. or json.

view this post on Zulip Richard Feldman (Feb 18 2024 at 21:48):

and the main module defines what URLs those correspond to

view this post on Zulip Brian Carroll (Feb 18 2024 at 21:52):

Hmm. Just thinking about why other languages don't have this problem. They mostly have package info in config. And if that mapping was in some static config file instead of Roc code then it could be used as context while compiling any module.

view this post on Zulip Richard Feldman (Feb 18 2024 at 21:55):

they usually have a hardcoded separate file like package.json or elm.json.

But a downside of that design is that you can't ship someone a single standalone .roc script that can be roc run unless it has no dependencies

view this post on Zulip Brian Carroll (Feb 18 2024 at 21:56):

Hmm yeah. But having to say what your main module is, feels like it's not very modular!

view this post on Zulip Richard Feldman (Feb 18 2024 at 22:02):

yeah. An idea I have mixed feelings about: if you roc check an interface module alone, then it basically assumes the types of all imported package things are _

view this post on Zulip Richard Feldman (Feb 18 2024 at 22:02):

so it type checks them for consistency against one another but that's it

view this post on Zulip Richard Feldman (Feb 18 2024 at 22:03):

unless you pass --main, in which case it resolves them using that module's defined shorthands

view this post on Zulip Brian Carroll (Feb 18 2024 at 22:04):

Yeah that occurred to me too and also mixed feelings. it seems a bit limited.

view this post on Zulip Brian Carroll (Feb 18 2024 at 22:04):

Could the dependency mapping syntax have two forms , one where the mapping is inline like today, and another where it references a .JSON file or something

view this post on Zulip Richard Feldman (Feb 18 2024 at 22:04):

yeah, it feels technically correct, but never what you actually want

view this post on Zulip Brian Carroll (Feb 18 2024 at 22:05):

Oh but actually the JSON file has to have a known standard name

view this post on Zulip Richard Feldman (Feb 18 2024 at 22:05):

eh if we're going to hardcode a filename I'd rather just hardcode the name main.roc

view this post on Zulip Richard Feldman (Feb 18 2024 at 22:05):

yeah exactly

view this post on Zulip Richard Feldman (Feb 18 2024 at 22:15):

an idea that seems reasonable to me, assuming that if you have a main.roc in the root of your project (because you'll want to run roc test and such from there), you'll have interface files for it inside something like src/ or roc/:

view this post on Zulip Richard Feldman (Feb 18 2024 at 22:15):

seems like it would make typical project structures Just Work, while still making it possible to have other project structures if you like

view this post on Zulip Richard Feldman (Feb 18 2024 at 22:24):

hm, although I guess typically an app module doesn't currently put interface modules in src/, but rather in the same directory as main.roc :thinking:

view this post on Zulip Agus Zubiaga (Feb 20 2024 at 11:05):

I missed this thread. We discussed the issue in December and reached the same conclusion: https://roc.zulipchat.com/#narrow/stream/395097-compiler-development/topic/module.20params/near/406294047

view this post on Zulip Eli Dowling (Feb 20 2024 at 11:41):

Not having any clear main.roc is kind of an issue from an lsp perspective. Perhaps we could enforce a naming scheme. If you wish your roc file to import any other modules then it needs to be in a project structure and be called "main.roc", however if it's just a script it can be called whatever but can't have any imports. You could even make a distinction in the package header "script" rather than "app" or "package".

This solves the lsp problem and the roc check problem, we just keep looking up directories until we find a "main.roc".
It also allows us to have script files and puts some sensible limits on them. Your script definitely shouldn't import any modules for example.

view this post on Zulip Luke Boswell (Feb 25 2024 at 02:24):

I made an issue for this #6537, @Agus Zubiaga is this something you are working on?

view this post on Zulip Richard Feldman (Feb 25 2024 at 03:08):

@Eli Dowling can you elaborate on what's different about this for LSP compared to command line?

view this post on Zulip Richard Feldman (Feb 25 2024 at 03:09):

for example, if roc check should Just Work (given CLI arg defaults) for a typical Roc project with main.roc in the same directory as the one where you're running roc check from, wouldn't the same logic work for an editor that has opened a root project directory containing main.roc?

view this post on Zulip Richard Feldman (Feb 25 2024 at 03:10):

hm, or does LSP not have a concept of "project directory" (or "current working directory" like a CLI run would) and instead just gets a path on the filesystem with no context?

view this post on Zulip Eli Dowling (Feb 25 2024 at 04:27):

So the lsp can determine the project root directory but it needs some way to do that.
Otherwise it will just open wherever you are currently. Normally for a language like JS it would look for a specific file "package.json" "cargo.toml" etc and use that to determine the root for the project.

The issue is, if we allow your main app/package file to be "myapp.roc" then we have no way of telling the language server that. For roc check it's fine to just have an arg, and if we can't find the expected "main.roc" we can just show a warning. Eg: "can't find a ain.roc file that is a package or app, interface imports won't be type checked, specify with '--main myapp.roc' "

The language server should "just work" and if it doesn't it's a real pain, you have config it separately for the project which is super painful and sucks, or set an environment variable which is fine if you use direnv or something, but is a hassle overall.

I'm not sure I see a very compelling argument for not just standardizing the name of the entry file in any multi-file program.

The only reasons I see not to:
You can have an app and a package import the same interfaces, or you can have multiple apps.
This seems like an anti-pattern, I would say if you want this your program should have a package, and an app that imports it.

It keeps the fact that there is no difference between single and multi-file roc programs.
I think there is actually a distinction, if you have multiple files, it's not just a script and should bae a project, if you only have one file that is a script it should be thought of differently.

Hopefully that clarifies :)

view this post on Zulip Eli Dowling (Feb 25 2024 at 04:28):

@Richard Feldman ^

view this post on Zulip Richard Feldman (Feb 25 2024 at 04:31):

Eli Dowling said:

So the lsp can determine the project root directory but it needs some way to do that.
Otherwise it will just open wherever you are currently.

hm, what does "wherever you are currently" mean in the context of an editor extension? Like let's say I'm running the vscode extension, and it boots up a roc language server - how would "wherever you are currently" get set in that context?

view this post on Zulip Eli Dowling (Feb 25 2024 at 04:32):

That's whatever path the editor was opened in.

view this post on Zulip Eli Dowling (Feb 25 2024 at 04:33):

But every language server can do it differently.
It knows:
The directory you opened the editor in.
The directory of the file just opened.
How it decides what to do with that is mostly up to the language server.

view this post on Zulip Eli Dowling (Feb 25 2024 at 04:41):

Some language servers do require that they be started within a project root. The editor is responsible for ensuring that if there are multiple roots within the one instance, eg you have two JS projects in one folder, that it will start two instances. (I'm not totally sure JS does this, but I know some do). Further your langserver can specify it supports multiple roots in which case your editor will send a message about the roots after initialisation( the JS server may do this)

view this post on Zulip Richard Feldman (Feb 25 2024 at 04:45):

hm, so I guess the scenario where maybe the place the editor opened wouldn't have main.roc might be if I'm in a big project in another language and Roc is being embedded in that project

view this post on Zulip Eli Dowling (Feb 25 2024 at 04:46):

But there has to be a main.roc or an "app" type file somewhere in a parent directory right?

view this post on Zulip Richard Feldman (Feb 25 2024 at 04:46):

yeah

view this post on Zulip Richard Feldman (Feb 25 2024 at 04:48):

I think the "main.roc is the required name if you want to import other files" idea is interesting, but I want to think about the implications more

view this post on Zulip Eli Dowling (Feb 25 2024 at 04:50):

So normally if it's opened somewhere in a subdirectory of a project the language server would walk up the file tree searching for the project root file eg: package.json then it can go from there. That's why having a specific name is valuable.

view this post on Zulip Richard Feldman (Feb 25 2024 at 04:51):

like for example, what would the rule be?

"app, package, and platform modules can only import other modules if they are named exactly main.roc, but otherwise they can be named anything. Normal modules can be named anything and can still import each other."

view this post on Zulip Richard Feldman (Feb 25 2024 at 04:52):

well keep in mind, "the default file to look for is named main.roc, but that can be configured in the editor" solves that too

view this post on Zulip Richard Feldman (Feb 25 2024 at 04:53):

it's a question of whether that's a convention encouraged by cli and ls defaults vs a language rule that cannot be broken

view this post on Zulip Eli Dowling (Feb 25 2024 at 04:57):

That would be my inclination, yeah. Then we can just provide a nice compile error explaining the correct structure.

I think it's also worth considering having a separate type of module for scripts. There may be other script specific things we could do. (I can't think of any right now, but just putting it out there)

I think it's much nicer and simpler to have the one right way, it also means any error can be more explicit. I'd be curious to hear a use case for having another name.

view this post on Zulip Richard Feldman (Feb 25 2024 at 05:09):

I don't have a specific use case in mind, it's just that it feels like the delta between a language guarantee and tooling default is really small in practice, and although the number of people who much want to actually use the flexibility might be small, given that the benefit of making it a language guarantee also seems super small, it feels like the payoff for the small subset of people who might actually benefit from the flexibility could be worth it

view this post on Zulip Richard Feldman (Feb 25 2024 at 05:10):

put another way, it feels like either way approximately everyone uses main.roc and the tooling expects it by default

view this post on Zulip Richard Feldman (Feb 25 2024 at 05:11):

so the main questions are "what if someone would genuinely benefit from another structure?" vs "what are the costs to tooling and to the language of supporting alternative options"

view this post on Zulip Richard Feldman (Feb 25 2024 at 05:12):

and it seems like it's a very minor deal to the tooling, but might be a bigger deal in a small minority of use cases

view this post on Zulip Richard Feldman (Feb 25 2024 at 05:18):

that said, a reasonable counterargument is that it's always easier to add flexibility later than to take it away later

view this post on Zulip Eli Dowling (Feb 25 2024 at 05:19):

I think I agree with all that, my main concern is first learning the language, having your language server just not work is really frustrating. I think with a good enough error message and all examples using main.roc and expressing that the other use is an edge case we should be able to mitigate that though.
I'm happy to implement it with the assumption that it should be "main.roc" and an option is available to change it.

The main things I want to clarify is that:

  1. Scripts, which would regularly not be named "main.roc" cannot have imports and this doesn't require changing language server settings. Meaning we can assume as the language server anything not named "main.roc"(or your configured main name) doesn't have imports.
  2. If we don't see a "main.roc"(or your configured main name) in the current directory, in a valid project we should expect to be able to walk up directories to find one.

view this post on Zulip Richard Feldman (Feb 25 2024 at 05:22):

totally agree on all counts!

view this post on Zulip Richard Feldman (Feb 25 2024 at 05:22):

let's proceed on those assumptions :big_smile:

view this post on Zulip Eli Dowling (Feb 25 2024 at 05:22):

Excellent!

view this post on Zulip Eli Dowling (Feb 25 2024 at 05:25):

Richard Feldman said:

that said, a reasonable counterargument is that it's always easier to add flexibility later than to take it away later

Also I agree with this, either way if we do keep it I would make it strongly an anti pattern to have any other name.
It just makes it harder to get a read on other projects and also really hard for others to use your project. They would have to configure their editor, and if they use a different editor it would be different for them etc etc.

view this post on Zulip Richard Feldman (Feb 25 2024 at 05:29):

totally

view this post on Zulip Richard Feldman (Feb 25 2024 at 05:29):

thanks for talking it through with me! :raised_hands:

view this post on Zulip Eli Dowling (Feb 25 2024 at 05:48):

I'm writing up an issue for this and just realized. roc check and roc could work in a subdirecty under these rules. They should just walk up till they find main.roc
Should I include that?

view this post on Zulip Richard Feldman (Feb 25 2024 at 05:56):

let's wait and see if there's demand in practice

view this post on Zulip Richard Feldman (Feb 25 2024 at 05:56):

easy enough to add it if so

view this post on Zulip Eli Dowling (Feb 25 2024 at 06:56):

Here is the issue : https://github.com/roc-lang/roc/issues/6538

view this post on Zulip Johannes Maas (Feb 25 2024 at 07:34):

Is it possible to read the first lines of each file? So in case there is no main.roc, it would open each file, look whether it is an app or package, and so if you have just one it could still just work.

If you have multiple, it could list them for you so that you can easily configure which one you want to use as the entry point.

Basically, the main.roc would be a strong convention, but it would work even if you deviate.

view this post on Zulip Johannes Maas (Feb 25 2024 at 07:36):

Also, could it be that you linked the wrong issue and meant this one? https://github.com/roc-lang/roc/issues/6538

view this post on Zulip Eli Dowling (Feb 25 2024 at 09:59):

@Johannes Maas
Thankyou, don't know how i cocked that up :sweat_smile:

My general instinct would be to disagree with that. Mostly because I like the simplicity of saying "It should always be main.roc unless you have a really good reason" and I thinking making it easier encourages people down a bad path.
Another advantage of keeping the structure simple is if someone wants to make some other tool that operates on roc projects they now also need to implement this automatic identification feature to be inline with the compiler.

That said, do you have any arguments for why we should allow having different names? My main objection is that I don't think it really adds value, but does and some complexity and makes each project slightly less standardized which i think is always kind of bad.

view this post on Zulip Johannes Maas (Feb 25 2024 at 13:18):

Good point! I also reflected and I think there should be only one main.roc for a package or app anyway, since I don't see any reason why you want to use different imports. So there is one entry point which by convention we can call main.roc.

And as far as I understand the current suggestions, if someone wants to deviate from that name, they can do so and only need to point the compiler or the LSP to the desired entrypoint. You've convinced me that this approach is much simpler and would nudge people in the right direciton by giving messages such as "I couldn't find the main.roc. Please make sure it exists or call me with --entry-point=<your entry point>."

view this post on Zulip Luke Boswell (Feb 25 2024 at 19:06):

An example of multiple main that might help is the Virtual Dom example. It's not working rn, but the idea is that both the client and server are both mains. Might be worth looking at just to see how it would work with this new design.

view this post on Zulip Luke Boswell (Feb 25 2024 at 19:07):

https://github.com/roc-lang/roc/tree/main/examples/virtual-dom-wip

view this post on Zulip Johannes Maas (Feb 25 2024 at 19:30):

Interesting. So if the interface were to depend on another package, then each app could theoretically import a different one.

view this post on Zulip Eli Dowling (Feb 25 2024 at 21:01):

Luke Boswell said:

the idea is that both the client and server are both mains.

Do you think that after the module Params changes it would make more sense to have all the code in exampleapp.roc be a package and then have the server and client import it, passing in what the exampleApp needs from the platform?

view this post on Zulip Luke Boswell (Feb 26 2024 at 00:29):

Though I'm not sure how ergonomic it would be to pass in pf.Html.{ App, Html, html, head, body, div, text, h1 } as a module param...

view this post on Zulip Luke Boswell (Feb 26 2024 at 00:31):

I haven't fully grokked module params, but would the Html package be imported by this package, and so it wouldn't need to be passed in?

view this post on Zulip Luke Boswell (Feb 26 2024 at 00:31):

There aren't any effects, so I guess it doesn't need anything from the platform.

view this post on Zulip Eli Dowling (Feb 26 2024 at 00:33):

I was just typing that out :sweat_smile:. I would imagine the html dsl would be its own package without any platform dependencies, so you wouldn't need to pass all that from the platform.

view this post on Zulip Brendan Hansknecht (Feb 26 2024 at 01:03):

Yeah, pretty sure most things will always be standalone packages that can be imported anywhere. It is just effects and special injections that would be passed in as module params.

view this post on Zulip Eli Dowling (Feb 26 2024 at 01:10):

Thanks for the confirmation :).


Last updated: Jun 16 2026 at 16:19 UTC