Stream: beginners

Topic: Parsing command line arguments


view this post on Zulip Aurélien Geron (Jul 28 2024 at 10:35):

Can someone please help me with a simple task? I'm trying to write a program that takes a list of integers on the command line, and prints their sum. Something roughly equivalent to this Python code:

import sys
print(sum([int(arg) for arg in sys.argv[1:]]))

I wrote the following program, but Roc complains about the type of parseNumber, and I'm lost.

app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }

import pf.Stdout
import pf.Task
import pf.Arg

parseNumber = \arg ->
    when Str.toU8 arg is
      Ok number -> Task.ok number
      Err msg -> Task.err (Exit 1 "Invalid argument \"${arg}\": ${msg}")

parseNumbers = \args ->
    args |> List.dropFirst 1 |> List.map parseNumber!

main =
    numbers = Arg.list! {} |> parseNumbers!
    total = List.sum numbers
    Stdout.line! "Sum of numbers: $(Num.toStr total)"

Thanks!

view this post on Zulip Luke Boswell (Jul 28 2024 at 10:44):

Here's one way to do it

app [main] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }

import pf.Stdout
import pf.Task
import pf.Arg

parseNumbers : List Str, List U8 -> Result (List U8) [InvalidArgument Str]
parseNumbers = \args, acc ->
    when args is
        [] -> Ok acc
        [first, .. as rest] ->
            when Str.toU8 first is
                Ok number -> parseNumbers rest (List.append acc number)
                Err InvalidNumStr -> Err (InvalidArgument "Got invalid argument $(first)")

expect parseNumbers ["1", "2", "3"] [] == Ok [1, 2, 3]
expect parseNumbers ["1", "2", "a"] [] |> Result.isErr

main =
    args = Arg.list! {}

    numbers =
        args
        |> List.dropFirst 1
        |> parseNumbers []
        |> Task.fromResult!

    total = List.sum numbers

    Stdout.line! "Sum of numbers: $(Num.toStr total)"

edit: added the drop first arg which is the program name

$ roc parseNumbers.roc -- 1 2 3 4
Sum of numbers: 10

view this post on Zulip Luke Boswell (Jul 28 2024 at 10:50):

This is a slightly more complicated to support the early return with an error.

If you are happy just mapping the argument List Str into a List U8 and only keeping the valid ones its easier.

args =
    Arg.list {}
    |> Task.map \argStrs -> argStrs |> List.keepOks Str.toU8

view this post on Zulip Richard Feldman (Jul 28 2024 at 13:40):

I think that should be Str.toUtf8 at the end there (not toU8)

view this post on Zulip Isaac Van Doren (Jul 28 2024 at 14:41):

I think Str.toU8 is the right idea, but probably would be better to use a larger integer type

view this post on Zulip Isaac Van Doren (Jul 28 2024 at 14:41):

In context it could be like this

main =
    args = Arg.list! {}
    total =
        args
        |> List.dropFirst 1
        |> List.keepOks Str.toI64
        |> List.sum
    Stdout.line "The sum is $(total |> Num.toStr)"

view this post on Zulip Aurélien Geron (Jul 29 2024 at 05:19):

Thanks guys, that's very helpful! :big_smile:
My plan is to solve Advent-of-Code 2020 using Roc, so the numbers I'm trying to parse are actually days, from 1 to 25, hence the U8. I don't actually need to compute their sum, but it was simpler to explain.

In case you're interested in the preliminary impressions of a complete newbie (just as a data point), here are my thoughts so far:

view this post on Zulip Luke Boswell (Jul 29 2024 at 05:24):

Thank you for the feedback, this is very helpful.

I wish I could hover over a def in VSCode and get its type.

You should be able to using the LS and VScode extension from @Ivan Demchenko which I just installed from the marketplace.

Here's a demo
Screenshot-2024-07-29-at-15.21.08.png

view this post on Zulip Luke Boswell (Jul 29 2024 at 05:25):

I don't think I had to do anything other than set the Path to the LS executable in the extension settings

view this post on Zulip Brendan Hansknecht (Jul 29 2024 at 05:40):

I think this is very dependent on author and target audience. For example, more compact forms of writing may be hard to follow as a beginner so they might not get written as often here.

That said:

  1. We definitely have gaps in the standard library/other helper libs to shorten code.
  2. I do think that by default roc leans more verbose than many other languages. I think it is mostly try to be explicit or clear (or just formatting with more spaces), but still more verbose.

view this post on Zulip Luke Boswell (Jul 29 2024 at 05:44):

I 100% agree the language is more verbose... but I don't think this is a bad thing either. I also had the same first impression, though it's hard for me to be unbiased now I've spent so much time writing roc.

One thing I've noticed from doing AoC and reading other people's solutions is that mine are generally 2-4x longer. I find some people are exceptionally good at writing much shorter solutions than I.

I often enjoy polishing my code, and playing a bit of golf, but I still find myself writing in a much more verbose style. I'm not sure why this is, but I find it interesting to think about.

view this post on Zulip Brendan Hansknecht (Jul 29 2024 at 05:45):

Taking parseNumbers for example, it could be written as:

parseNumbers : List Str-> Result (List U8) [InvalidNumStr]
parseNumbers = \args ->
    List.mapTry Str.toU8

Though if you want the same error type, it would be:

parseNumbers : List Str-> Result (List U8) [InvalidArgument Str]
parseNumbers = \args ->
    List.mapTry \str ->
        Str.toU8 str |> Result.mapErr (\_ -> InvalidArgument str)

view this post on Zulip Luke Boswell (Jul 29 2024 at 05:47):

I'm already a bit tired of typing List. all the time

I'm really looking forward to one day having editor tooling that can autodetect imports from the Builtins. So I could just write toU8 and the editor might auto add List. prefix and also import it for me.

I think it's something we've discussed before.

view this post on Zulip Luke Boswell (Jul 29 2024 at 05:48):

List.mapTry Str.toU8Checked

Oh nice. I hadn't thought of that.

view this post on Zulip Brendan Hansknecht (Jul 29 2024 at 05:49):

This whole thing could be (assuming desugaring of ! works correctly, might not yet):

main =
    Arg.list! {}
    |> List.dropFirst 1
    |> List.mapTry Str.toU8
    |> Task.fromResult!
    |> List.sum
    |> \total -> "Sum of numbers: $(Num.toStr total)"
    |> Stdout.line!

Though maybe that is too much |>...haha

view this post on Zulip Brendan Hansknecht (Jul 29 2024 at 05:55):

such as a function to run multiple tasks sequentially and return the first error if any

Just a note, this actually does exist: https://www.roc-lang.org/packages/basic-cli/0.12.0/Task#sequence

view this post on Zulip Anton (Jul 29 2024 at 09:33):

Thanks for the great feedback @Aurélien Geron!

The docs and examples are not fully in sync with the latest release. In particular, I struggled to get Arg.list! to work

I'd like to fix this, can you tell me where to find these outdated docs/examples?

view this post on Zulip Anton (Jul 29 2024 at 09:34):

The ones I looked at are up to date but I must have missed something

view this post on Zulip Anton (Jul 29 2024 at 09:41):

:cross_mark: The language seems very verbose

One thing that makes Roc more verbose is our encouragement of error handling. print(sum([int(arg) for arg in sys.argv[1:]])) would crash if you provide an arg that is not an int but python does not encourage you to catch this.

view this post on Zulip Anton (Jul 29 2024 at 09:42):

In advent of code you just want to get things done quickly, but you want real world code to be reliable.

view this post on Zulip Anton (Jul 29 2024 at 09:45):

:cross_mark: The REPL doesn't feel very useful yet, since it doesn't have any effects.

Effects for the REPL are planned, anybody know if we have an issue for this?

view this post on Zulip Anton (Jul 29 2024 at 10:05):

print(sum([int(arg) for arg in sys.argv[1:]])) would also return 0 if the user did not enter any args but that is not ideal behavior for real software. You probably want to present an error message to the user in this case.

view this post on Zulip Luke Boswell (Jul 29 2024 at 10:09):

anybody know if we have an issue for this?

I don't know if we do. But @Bram's work in https://roc.zulipchat.com/#narrow/stream/316715-contributing/topic/playground.2Frepl.20with.20LSP.20support/near/453747271 is very closely related I think. He's looking at the problem from the WASM perspective, but enabling effects in WASM is very similar to the REPL I'm guessing.

view this post on Zulip Richard Feldman (Jul 29 2024 at 12:10):

this should work, but doesn't because of a compiler bug (that we discussed elsewhere :sweat_smile:)

main =
    Arg.list! {}
    |> List.mapTry Str.toU8
    |> Task.fromResult!
    |> List.sum
    |> Num.toStr
    |> Stdout.line!

view this post on Zulip Anton (Jul 29 2024 at 12:57):

:cross_mark: The compiler's error messages are not always clear enough. For example, I really got 100% stuck on the simple problem above, despite my best efforts. Try running my code to see what I mean. Perhaps the tutorial needs more examples of Task / Result interaction.

I feel like a good tip here would be to recommend adding type annotations, which is how I fixed the code. Should we add that as a tip to all these opaque type mismatches?

view this post on Zulip Brendan Hansknecht (Jul 29 2024 at 15:51):

Just like a little addendum? "note: adding type annotations may help improve and narrow down this error message`

view this post on Zulip Aurélien Geron (Jul 31 2024 at 10:20):

Anton said:

Thanks for the great feedback Aurélien Geron!

The docs and examples are not fully in sync with the latest release. In particular, I struggled to get Arg.list! to work

I'd like to fix this, can you tell me where to find these outdated docs/examples?

Sorry for the late reply. I can't find any sync issues anymore, so I suppose I must have mixed up the versions I was looking at, perhaps using basic-cli version 0.11 instead of 0.12. I'll let you know if I find anything, but for now this looks more like a brain fart on my part. :woozy_face:


Last updated: Jul 05 2025 at 12:14 UTC