Originally basic-cli had the name main because that's kind of the standard starting point name from C, Elm, etc.
it's kind of strange to see it named main! though, because functions ending in ! are always verbs
and although main is the common name for the entrypoint function, main! is not - so we're doing something different from the norm no matter what
maybe it should be a verb like run! or start! or init! instead?
I like run!
Probably should just stick with main!
But run would be a good alternative
run seems more beginner friendly
I agree that absolute beginners don't know about main as the convention
For those devs, run is definitely better
Everyone that has used Python will have seen main or __main__
I don't have that strong of an opinion here. Like the other thread, this is a very minor benefit, which means it's better to not add to the weirdness budget here if we don't receive much benefit
But any experienced dev will figure it out in 30 seconds, or less with editor integration
the thing is, main! is already weird
plenty of languages say main but nobody says main!
I think if there weren't the ! then main would be the obvious choice, but the fact that it needs to be main! makes me think it's worth reconsidering
another thing is that main! is potentially reasonable for basic-cli, where you're just running what's in there
but it's not the best name for a request handler in basic-webserver
so since some platforms should have different entrypoint names anyway, maybe we shouldn't try to have a "standard entrypoint name" (such as main) and just always give a name that's specific to what the platform actually does in that entrypoint
also, without even thinking about surrounding context, in isolation run! = looks fine to me but main! = looks weird :big_smile:
What about the convention of main.roc then?
the filename is fine I think
no concerns there
I always thought it would be nice if the entry file for an app was app.roc, a package package.roc, and a platform platform.roc
But I don’t really care that much
But it would make it easy to understand what kind of Roc project you are looking at without opening a file
Richard Feldman said:
plenty of languages say
mainbut nobody saysmain!
They would if they had Roc's rule for effects in the name. I don't think there are a lot of main with no side effects.
sure, but nobody else does that right now, so it makes it weird :big_smile:
btw the actual reasoning for the main.roc convention is that:
roc test and roc check without having to specify a filenamepackage.json or Cargo.toml equivalent - you should always be able to build a roc application (and distribute it as a standalone single-file script, just like how scripts are always distributed), even if it has package dependencies, using a single .roc file as the entrypoint and that's itmain.roc addresses all of these (regardless of whether you're working on an application, or a platform, or a package, all of which should ideally have roc check and roc test Just Work without needing further arguments) by making it really straightforward to understand what filename is being provided if you don't provide one (it's "main.roc"!)
I don't get it but I don't care which color this bikeshed is painted.
I like run! also
Richard Feldman said:
so since some platforms should have different entrypoint names anyway, maybe we shouldn't try to have a "standard entrypoint name" (such as main) and just always give a name that's specific to what the platform actually does in that entrypoint
I personally kind of like the idea of there being some entry point naming convention that's shared across platforms. Letting each platform pick their own name for the entry point feels like one more thing that you have pick up / configure / document. And I think it'd be kind of nice to try out a new platform and have the natural first question be: "What's the type of main for this platform?".
(substitute main for any other name, that's not the important bit for me)
I can see that point, but to me it's weird to have my code be like main! = |request| ... as opposed to e.g. handle_request! : |request| ...
I didn't realize main.roc was a standard.
I'm working on a static site generation platform. Unaware of the main.roc I've used build.roc as a conventional entry-point in docs so far. My thinking was that even someone unfamiliar with Roc or my static site generation platform would be able to intuit that build.roc probably is responsible for building the site, and then learn more from there.
Similarly, the next platform I'm planning to do is a CI platform, and I was thinking ci.roc would be a good entry-point convention for that. That has the same self-descriptiveness-benefits as the build.roc example, plus it means you can have a ci.roc and a build.roc/main.roc/app.roc in the same directory.
Anyway, the argument for having a main.roc convention if you call Roc without extra arguments makes sense to me too, maybe I might change build.roc based on those insights. But wanted to share some of my thoughts :).
Richard Feldman said:
I can see that point, but to me it's weird to have my code be like
main! = |request| ...as opposed to e.g.handle_request! : |request| ...
A standard entrypoint name is such a strong convention across languages though, is being different there worth the extra complexity? What I mean is that I can't imagine people being (unpleasantly) surprised by that.
Plus, won't most platforms define their entry points to take records? Especially if you want to do the Elm thing where you offer a couple of different "application constructor functions" of increasing complexity, that seems incompatible with handle_request! = .. entrypoints, no?
that's a good point - I think it's hard to predict how common those will be :big_smile:
I don't know how about the Elm constructor pattern in particular, but most platforms needing to take multiple configuration values to start in a record seems a pretty safe bet though, right? Maybe scripts are different.
I've used
build.rocas a conventional entry-point in docs so far
Just fyi, build.roc is the name we use for the file that builds the platform https://github.com/roc-lang/basic-cli/blob/main/build.roc
I am in the team, that each platform has its own name. At least, if the platform provides more than one symbol, there is no way around it. For example my AoC platform exposed part1 and part2.
I also hope, that there will be a better solution in the future, that the main file does not have to be called main.roc. If you have an directory with more then one Roc script, only one of them can be called main.roc. I had this situation many times and it is annoying, that the LSP cannot work with dependencies, if the entry file is not called main.roc.
Theres a cli arg --main to point it at something not named main.roc. I wonder if we could do something similar for the LSP?
One plus for run! is that it's a bit different -- and in that way people might pause and reflect on it and think about that. It's a unique part of the platform/app abstraction and a subtle difference between a typical cli or webserver program and a roc app. This isn't bare metal programming... you're in a high level sandbox for a particular domain.
aside: let's have a separate thread about main.roc if we want to discuss that :big_smile:
In basic-webserver we switched to init! and respond! because it felt nicer than having a single main which returned a record. I like the simplification there, it feel like there is one less layer of abstraction.
I guess I personally prefer the domain specific names because it feels nicer.
Another data point - roc-ray graphics playform we have init! and render!.
And in the js-dom experiment (and the Joy) platform which is working towards Action-State it's init, update, and render (note they're pure functions).
A webserver needs to initialize and respond. A CLI needs to main run.
It makes me laugh though -
Me: Check out this new programming language.
Friend: Alright, let's see here...
Roc: run! save!(your.self)
If main.roc and main! aren't intentionally part of the same convention, renaming one seems like a good thing purely for clarity of patterns.
Luke Boswell said:
In basic-webserver we switched to
init!andrespond!because it felt nicer than having a singlemainwhich returned a record.
Is the plan to keep that in the long run? I thought there was an idea to combine them into a record, to allow the return value from init! be coupled with the argument passed to respond! using a regular type parameter.
When explaining Roc, I sometimes rotate 90° the conceptual stack of abstractions and describe Roc as a land of pure functions that Rust/etc calls out to (as ffi does) to perform an app's "business logic". This resonates weakly with exposing a "main function" but strongly with domain-specific function(s).
(Nothing novel there, but for 99% of Python developers, "main" is definitely atop a vertical conceptual stack, not a sideways one. Java too, it seems, though I haven't used it deeply.)
On the other hand, seeing a main function is an easy reference point for Python/Java/etc developers to have a smoother first impression of Roc!
Jasper Woudenberg said:
Luke Boswell said:
In basic-webserver we switched to
init!andrespond!because it felt nicer than having a singlemainwhich returned a record.Is the plan to keep that in the long run? I thought there was an idea to combine them into a record, to allow the return value from
init!be coupled with the argument passed torespond!using a regular type parameter.
I'm not tracking any plans.
I noticed Richard has a different API in the realworld app.
Instead of taking Model in as an arg, we've talked about using just a regular type variable. I assumed because these are returned in a "record" that would just work ok.
Model is an argplatform "webserver"
requires { Model } {
init! : {} => Result Model [Exit I32 Str]_,
respond! : Http.Request, Model => Result Http.Response [ServerErr Str]_,
}
...
model type varplatform "webserver"
requires {
init! : {} => Result model [Exit I32 Str]_,
respond! : Http.Request, model => Result Http.Response [ServerErr Str]_,
}
...
Yeah, I think they would have to be a record to link the type variables, but that is less nice of an api. So I wish for a different way to link the type variables in platforms
A different way to link type variable in platforms would also be needed if we want to allow for a save! : Box model -> {} and restore! : {} -> Box model style effect.
I know it's a bit of a tangent, but I'm still not convinced that API is a good idea
like yeah it would feel nice to use, but if you're putting your entire app state in there and updating it in web requests, it's going to run into contention problems as soon as enough concurrent requests start running
maybe there's a variation of the API that still seems nice but doesn't have that problem (e.g. based around queues) but I don't want to consider something a motivating use case if it actually seems like a potential performance footgun :sweat_smile:
I don't particularly care about the specific example of the webserver. I just like the general api ability. There is currently no way to link data in the main exposed functions to data used by effects and that feels like a missing features. It is also not particularly convenient to link data supplied to multiple main functions.
that's fair, but I think it's something that needs a motivating use case before we should try to support it
right now I think it's mostly a downside - not just because it's a semantic addition to the language, but also because it makes it easier to make an API that has no observable performance problems when you're testing your webserver locally, but then runs into serious contention problems when you get your first traffic spike
I really don't want to set people up for an experience where they get burned like that :sweat_smile:
if it turns out there are some specific use cases where it's really valuable, I could see that outweighing the footgun-in-other-scenarios downside
but I'd want to look at what those specific scenarios actually are, what the alternative APIs would look like, etc.
Brendan Hansknecht said:
Yeah, I think they would have to be a record to link the type variables, but that is less nice of an api.
I guess I don't really understand this part. There just (to me) sound like so many downsides to offering a separate API for exposing multiple "main" entries:
I understand the appeal of custom-named-main(s) from a general desire to have descriptive names everywhere extended to the application entry point(s). But I guess I'm having trouble coming up with a scenario where an app author is going to be more productive, less confused, or learns more quickly because of the custom entrypoint names feature.
Richard Feldman said:
right now I think it's mostly a downside - not just because it's a semantic addition to the language, but also because it makes it easier to make an API that has no observable performance problems when you're testing your webserver locally, but then runs into serious contention problems when you get your first traffic spike
I don't think this is true. If I understand you correctly, you are saying, that when you have your data behind a lock/mutex, then the webserver is slow on traffic spikes.
Take redis for an example. Redis is an inmemory database that is basically single threaded. But nobody would say, that redis runs into serious problems on traffic spikes. I think the contrary is true. You bring in redis (or an opensource alternative) when you get your first traffic spike.
Another example is SQLite. SQLite allows parallel read transactions but only one simultaneous write transaction. There is debate, if SQLite is fast enough to be used as the main database. But it seems fast enough, that a popular Web-Framework supports it :wink:. I don't think, that SQLite should be removed from basic-webserver, because it is a footgun on the first traffic spike.
At the moment, having inmemory data in a roc webserver is slower, then it has to be. At the moment, that lock/mutex has to be set in the host, before Roc is called
set_lock(
call_roc(
do_application_logic,
save_data,
)
)
If Roc would support effects that can access the data, this could be changed to:
call_roc(
do_application_logic,
set_lock(
save_data,
)
)
If you accept, that inmemory data can be fast, at least in some usecases, then it can be a big win. There is an interview from Kris Jenkins (Developers Voices) with Evan Czaplicki. In around minute 14 Even is talking about the gain for functional programming, when type safety is flowing around the hole stack. So when you make a change to the database, you can see nice error messages on the frontend. He is saying, that this could be huge and that is, what the companies he is talking to, really want.
I think that this can already accomplished with Roc+htmx when you safe the data in a Model. With htmx, there is no client state, all state is on the server. When you don't have a separate database, then Roc guaranties, the type safety throw the hole stack. Its like an ORM but without the abstraction cost.
In conclusion, I think a webserver with typed inmemory data is not a footgun, but should be explored further.
wow, TIL about redis being single-threaded! I want to read up more about this, because that definitely changes some assumptions I had :astonished:
Redis definitely runs into traffic spikes :sweat_smile: but I've solved that in other ways, by sharding it or reducing write load (all operations are async and reads are always fine)
You can do a lot with a single thread and smart io handling. Redis just has to be able to hit the limits of the network card and it is only a kv lookup, so generally does very little cpu work.
Last updated: Jun 16 2026 at 16:19 UTC