Stream: ideas

Topic: is `main!` a good name?


view this post on Zulip Richard Feldman (Jan 23 2025 at 16:09):

Originally basic-cli had the name main because that's kind of the standard starting point name from C, Elm, etc.

view this post on Zulip Richard Feldman (Jan 23 2025 at 16:09):

it's kind of strange to see it named main! though, because functions ending in ! are always verbs

view this post on Zulip Richard Feldman (Jan 23 2025 at 16:10):

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

view this post on Zulip Richard Feldman (Jan 23 2025 at 16:10):

maybe it should be a verb like run! or start! or init! instead?

view this post on Zulip Anton (Jan 23 2025 at 16:20):

I like run!

view this post on Zulip Sam Mohr (Jan 23 2025 at 16:44):

Probably should just stick with main!

view this post on Zulip Sam Mohr (Jan 23 2025 at 16:44):

But run would be a good alternative

view this post on Zulip Anton (Jan 23 2025 at 16:46):

run seems more beginner friendly

view this post on Zulip Sam Mohr (Jan 23 2025 at 16:49):

I agree that absolute beginners don't know about main as the convention

view this post on Zulip Sam Mohr (Jan 23 2025 at 16:50):

For those devs, run is definitely better

view this post on Zulip Sam Mohr (Jan 23 2025 at 16:50):

Everyone that has used Python will have seen main or __main__

view this post on Zulip Sam Mohr (Jan 23 2025 at 16:51):

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

view this post on Zulip Sam Mohr (Jan 23 2025 at 16:52):

But any experienced dev will figure it out in 30 seconds, or less with editor integration

view this post on Zulip Richard Feldman (Jan 23 2025 at 17:04):

the thing is, main! is already weird

view this post on Zulip Richard Feldman (Jan 23 2025 at 17:04):

plenty of languages say main but nobody says main!

view this post on Zulip Richard Feldman (Jan 23 2025 at 17:05):

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

view this post on Zulip Richard Feldman (Jan 23 2025 at 17:07):

another thing is that main! is potentially reasonable for basic-cli, where you're just running what's in there

view this post on Zulip Richard Feldman (Jan 23 2025 at 17:07):

but it's not the best name for a request handler in basic-webserver

view this post on Zulip Richard Feldman (Jan 23 2025 at 17:08):

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

view this post on Zulip Richard Feldman (Jan 23 2025 at 17:14):

also, without even thinking about surrounding context, in isolation run! = looks fine to me but main! = looks weird :big_smile:

view this post on Zulip Anthony Bullard (Jan 23 2025 at 17:31):

What about the convention of main.roc then?

view this post on Zulip Richard Feldman (Jan 23 2025 at 17:31):

the filename is fine I think

view this post on Zulip Richard Feldman (Jan 23 2025 at 17:31):

no concerns there

view this post on Zulip Anthony Bullard (Jan 23 2025 at 17:32):

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

view this post on Zulip Anthony Bullard (Jan 23 2025 at 17:32):

But I don’t really care that much

view this post on Zulip Anthony Bullard (Jan 23 2025 at 17:32):

But it would make it easy to understand what kind of Roc project you are looking at without opening a file

view this post on Zulip Karl (Jan 23 2025 at 17:35):

Richard Feldman said:

plenty of languages say main but nobody says main!

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.

view this post on Zulip Richard Feldman (Jan 23 2025 at 17:35):

sure, but nobody else does that right now, so it makes it weird :big_smile:

view this post on Zulip Richard Feldman (Jan 23 2025 at 17:36):

btw the actual reasoning for the main.roc convention is that:

main.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"!)

view this post on Zulip Karl (Jan 23 2025 at 17:36):

I don't get it but I don't care which color this bikeshed is painted.

view this post on Zulip Isaac Van Doren (Jan 23 2025 at 17:58):

I like run! also

view this post on Zulip Jasper Woudenberg (Jan 23 2025 at 18:02):

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)

view this post on Zulip Richard Feldman (Jan 23 2025 at 18:08):

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| ...

view this post on Zulip Jasper Woudenberg (Jan 23 2025 at 18:09):

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 :).

view this post on Zulip Jasper Woudenberg (Jan 23 2025 at 18:13):

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?

view this post on Zulip Richard Feldman (Jan 23 2025 at 18:15):

that's a good point - I think it's hard to predict how common those will be :big_smile:

view this post on Zulip Jasper Woudenberg (Jan 23 2025 at 18:19):

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.

view this post on Zulip Anton (Jan 23 2025 at 18:27):

I've used build.roc as 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

view this post on Zulip Oskar Hahn (Jan 23 2025 at 18:57):

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.

view this post on Zulip Luke Boswell (Jan 23 2025 at 19:02):

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?

view this post on Zulip Luke Boswell (Jan 23 2025 at 20:38):

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.

view this post on Zulip Richard Feldman (Jan 23 2025 at 20:40):

aside: let's have a separate thread about main.roc if we want to discuss that :big_smile:

view this post on Zulip Luke Boswell (Jan 23 2025 at 20:50):

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).

view this post on Zulip jan kili (Jan 23 2025 at 20:57):

A webserver needs to initialize and respond. A CLI needs to main run.

view this post on Zulip jan kili (Jan 23 2025 at 21:00):

It makes me laugh though -
Me: Check out this new programming language.
Friend: Alright, let's see here...
Roc: run! save!(your.self)

view this post on Zulip jan kili (Jan 23 2025 at 21:06):

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.

view this post on Zulip Jasper Woudenberg (Jan 23 2025 at 21:10):

Luke Boswell said:

In basic-webserver we switched to init! and respond! because it felt nicer than having a single main which 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.

view this post on Zulip jan kili (Jan 23 2025 at 21:17):

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).

view this post on Zulip jan kili (Jan 23 2025 at 21:20):

(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.)

view this post on Zulip jan kili (Jan 23 2025 at 21:28):

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!

view this post on Zulip Luke Boswell (Jan 23 2025 at 22:05):

Jasper Woudenberg said:

Luke Boswell said:

In basic-webserver we switched to init! and respond! because it felt nicer than having a single main which 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.

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.

current API Model is an arg

platform "webserver"
    requires { Model } {
        init! : {} => Result Model [Exit I32 Str]_,
        respond! : Http.Request, Model => Result Http.Response [ServerErr Str]_,
    }
    ...

possible future using model type var

platform "webserver"
    requires {
        init! : {} => Result model [Exit I32 Str]_,
        respond! : Http.Request, model => Result Http.Response [ServerErr Str]_,
    }
    ...

view this post on Zulip Brendan Hansknecht (Jan 24 2025 at 02:39):

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

view this post on Zulip Brendan Hansknecht (Jan 24 2025 at 02:39):

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.

view this post on Zulip Richard Feldman (Jan 24 2025 at 03:30):

I know it's a bit of a tangent, but I'm still not convinced that API is a good idea

view this post on Zulip Richard Feldman (Jan 24 2025 at 03:32):

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

view this post on Zulip Richard Feldman (Jan 24 2025 at 03:33):

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:

view this post on Zulip Brendan Hansknecht (Jan 24 2025 at 03:49):

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.

view this post on Zulip Richard Feldman (Jan 24 2025 at 03:58):

that's fair, but I think it's something that needs a motivating use case before we should try to support it

view this post on Zulip Richard Feldman (Jan 24 2025 at 03:59):

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

view this post on Zulip Richard Feldman (Jan 24 2025 at 03:59):

I really don't want to set people up for an experience where they get burned like that :sweat_smile:

view this post on Zulip Richard Feldman (Jan 24 2025 at 04:00):

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

view this post on Zulip Richard Feldman (Jan 24 2025 at 04:00):

but I'd want to look at what those specific scenarios actually are, what the alternative APIs would look like, etc.

view this post on Zulip Jasper Woudenberg (Jan 24 2025 at 07:48):

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.

view this post on Zulip Oskar Hahn (Jan 24 2025 at 07:49):

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.

view this post on Zulip Richard Feldman (Jan 25 2025 at 12:08):

wow, TIL about redis being single-threaded! I want to read up more about this, because that definitely changes some assumptions I had :astonished:

view this post on Zulip Ayaz Hafiz (Jan 25 2025 at 16:46):

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)

view this post on Zulip Brendan Hansknecht (Jan 25 2025 at 18:58):

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