so originally the examples/cli-platform API for defining main was that you'd give it main : Task {} [] *
I changed it to be main : Program like Elm does, which I saw as having these tradeoffs:
|> Program.noArgs in addition to making your TaskTaskmain instead of having to run Tasks to get themProgram.quick : Task * * * -> Program which lets you write a CLI app that doesn't bother handling errorsI saw some feedback in #roctoberfest > Show and Tell that Program didn't feel great to use, and I'm curious how people feel about it
for example, an alternate design would be:
main : Task {} [] *Arg.list : Task (List Str) * [Read [Args]*]* be the way to get CLI args (and probably also an Arg.walk and Arg.parseAll : Arg.Parser a -> Task a [ParseErr Arg.ParseErr]* [Read [Args]*]*), and tasks like Env.dict : Task (Dict Str Str) * [Env]* be the way to get environment variablesThat alternative sounds nice. It's similar to how Rust does command line arguments and environment variables.
Maybe having arguments to main is just not a big enough advantage to be worth more concepts.
The ability to ignore errors.... Hmm... I'm not sure about that as an advantage. I'm generally ok with being forced to handle them. That's one of the reasons to use a language like this. And you can usually do Result.withDefault if you want a shortcut
tho I understand the pain - as I also had them. I believe this is mainly due to poor editor integration at the moment. It is a lot harder to understand what needs to be passed because we don't get easy access to module documentation / go to definition / etc. and when first setting up things the compiler is not as helpful since you may be too far from compilation to get helpful error messages.
my point is that I'd take current pains with a grain of salt as those might be more caused be things mentioned than the API itself.
I think that i like program. Just took me a bit to figure out how to use it because it was something to learn. Had to read through the file some.
Renaming it to Cli or CliProgram might make it feel more necessary, significant, and final
That way the feeling goes from "how many more Roc words am I going to need to learn?!" to "ooh, this is the big green button for this particular platform"
Hmm... I like that you brought up the word "necessary"! For example I think it's more necessary in Elm, where a Program is made from several user-defined things. So having a name for that bundle is worthwhile. But here, the Program only made from one user-defined thing. So there's a bit less justification for having it.
Having said that, I don't want to sound more opposed to Program than I am. I actually think it's fine!
Maybe having this concept across many platforms would help it to carry a bit more weight. Like maybe the Program for a GUI platform would contain several user-defined functions like Elm does. And I don't know what a Program for a web server platform might look like either.
One big problem for me is the incomprehensibility of the error messages around Program. I get this each time I misuse await (though I'm still trying to figure out the right way to use await in Programland... I may be TOO used to the old way)
This 1st argument to toEffect has an unexpected type:
9│ mainForHost = InternalProgram.toEffect main
^^^^
This #UserApp.main value is a:
Task.Task b err fx ?
But toEffect needs its 1st argument to be:
InternalProgram.InternalProgram ?
We shouldn't expose a beginner app developer to half of the words in that message when they're writing one of their first apps
I agree that struggling with the Program + Task/Effect is a bit frustrating.
But that's also something we need to walk through when learning a new language.
I don't know if a beginner like me is able to see all the implications of this potential change but going back to this simpler API would be welcomed for me.
I think the word main is getting overloaded, and also it's confusing to instantiate an opaque type by chaining other opaque types together. For example, something like
...
imports [pf.Cli]
provides [cli] to pf
cli = Cli.build { args, run }
args = ...
run = ...
feels much more approachable and intuitive. (I haven't thought through the exact syntax here, just rough inspiration)
I think once it is setup/you know it, it works well. Setup is more trial and error and eh
I think better error messages and platform web documentation would solve most of the friction.
So I don't think it is an issue with Program for the most part
I notice a trend of the case for Program not being "yes there are downsides, but the upsides are worth it" but rather "the downsides aren't that bad, or can be mitigated"
which is to say, it seems like the main case for it is not "it's good" but rather "it's not that bad, so maybe we don't need to bother changing it" :big_smile:
so given how early on we are, that suggests to me that changing back would be the better choice!
the API is strictly simpler without it, and there are other downsides, but the upsides seem really minimal in comparison
Last updated: Jun 16 2026 at 16:19 UTC