Stream: beginners

Topic: ✔ How to Stdout.line! inside a Result-returning block?


view this post on Zulip jan kili (Jan 02 2025 at 15:26):

(This might be specific to basic-cli@0.18.0.) I have a function that returns a custom Result, and during its construction I want to use Stdout.line! for info. However, this causes a type mismatch where the Result the block returns conflicts with the Result that line! returns, with no obvious way to deconflict them. What are some small/large changes I can make to this minimal reproduction to get it to compile?

app [main!] {
    basic_cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br",
}

import basic_cli.Stdout

main! = \_ ->
    s = try get_string! {}

    Stdout.line! s

get_string! : {} => Result Str _
get_string! = \_ ->
    Str.fromUtf8 [100]
    |> \result ->
        when result is
            Err e ->
                when e is
                    BadUtf8 foo bar ->
                        try Stdout.line! "BadUtf8: $(Inspect.toStr foo) $(Inspect.toStr bar)"
                        Err "custom"

            Ok s ->
                Ok s
jan@framey:~/_code/JanCVanB/bug-repros/mixed-errs$ roc check

── TYPE MISMATCH in main.roc ───────────────────────────────────────────────────

This returns something that's incompatible with the return type of the
enclosing function:

16│           when result is
17│               Err e ->
18│                   when e is
19│                       BadUtf8 foo bar ->
20│>                          try Stdout.line! "BadUtf8: $(Inspect.toStr foo) $(Inspect.toStr bar)"
21│                           Err "custom"
22│
23│               Ok s ->
24│                   Ok s

This returns an Err of type:

    [Err [StdoutErr [
        AlreadyExists,
        BrokenPipe,
        Interrupted,
        NotFound,
        Other Str,
        OutOfMemory,
        PermissionDenied,
        Unsupported,
    ]], …]

But I expected the function to have return type:

    [Err Str, …]

────────────────────────────────────────────────────────────────────────────────

1 error and 0 warnings found in 28 ms.

jan@framey:~/_code/JanCVanB/bug-repros/mixed-errs$

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:33):

You need to use a tag error

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:34):

a Str error can not merge with the tag error being returned from Stdout.line!

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:34):

That or you need to map the Stdout.line! error into a Str

view this post on Zulip jan kili (Jan 02 2025 at 15:35):

Ah! Yes, Err (Custom "custom") resolves the type mismatch.

view this post on Zulip jan kili (Jan 02 2025 at 15:36):

Perhaps I need a different minimal reproduction for this that I'm hitting in my real app:

This returns an `Err` of type:

    [Err [StderrErr [
        AlreadyExists,
        BrokenPipe,
        Interrupted,
        NotFound,
        Other Str,
        OutOfMemory,
        PermissionDenied,
        Unsupported,
    ]], …]

But I expected the function to have return type:

    [Err [
        FileReadErr * *,
        FileReadUtf8Err * *,
    ], …]d

view this post on Zulip jan kili (Jan 02 2025 at 15:37):

because I assumed those two tag unions
( [StderrErr ...] and [FileReadErr ..., FileReadUtf8Err ...] )
would merge.

view this post on Zulip jan kili (Jan 02 2025 at 15:38):

Real app function:

read_file! : PathToFile => Result Str _
read_file! = \file_path ->
    file_path
    |> Path.read_utf8!
    |> \result ->
        when result is
            Err e ->
                when e is
                    FileReadErr path io_err ->
                        try Stderr.line! "📰 ❗ Failed to read file $(Path.display path) because $(Inspect.toStr io_err)"
                        try Stderr.line! tip_for_calling_this_app
                        Err e

                    FileReadUtf8Err path io_err ->
                        try Stderr.line! "📰 ❗ Failed to read file $(Path.display path) because $(Inspect.toStr io_err)"
                        Err e

            Ok file_contents ->
                Ok file_contents

view this post on Zulip jan kili (Jan 02 2025 at 15:40):

jan@framey:~/_code/JanCVanB/roc-typesetter$ roc check

── TYPE MISMATCH in main.roc ───────────────────────────────────────────────────

This returns something that's incompatible with the return type of the
enclosing function:

50│           when result is
51│               Err e ->
52│                   when e is
53│                       FileReadErr path io_err ->
54│>                          try Stderr.line! "📰 ❗ Failed to read file $(Path.display path) because $(Inspect.toStr io_err)"
55│                           try Stderr.line! tip_for_calling_this_app
56│                           Err e
57│
58│                       FileReadUtf8Err path io_err ->
59│                           try Stderr.line! "📰 ❗ Failed to read file $(Path.display path) because $(Inspect.toStr io_err)"
60│                           Err e
61│
62│               Ok file_contents ->
63│                   Ok file_contents

This returns an Err of type:

    [Err [StderrErr [
        AlreadyExists,
        BrokenPipe,
        Interrupted,
        NotFound,
        Other Str,
        OutOfMemory,
        PermissionDenied,
        Unsupported,
    ]], …]

But I expected the function to have return type:

    [Err [
        FileReadErr Path.Path val,
        FileReadUtf8Err Path.Path val,
    ], …] where val implements Inspect, val implements Inspect

────────────────────────────────────────────────────────────────────────────────

1 error and 0 warnings found in 32 ms.

jan@framey:~/_code/JanCVanB/roc-typesetter$

view this post on Zulip jan kili (Jan 02 2025 at 15:44):







This is a better minimal repro:

app [main!] {
    basic_cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.18.0/0APbwVN1_p1mJ96tXjaoiUCr8NBGamr8G8Ac_DrXR-o.tar.br",
}

import basic_cli.Stdout

main! = \_ ->
    s = try get_string! {}

    Stdout.line! s

get_string! : {} => Result Str _
get_string! = \_ ->
    Str.fromUtf8 [100]
    |> \result ->
        when result is
            Err e ->
                when e is
                    BadUtf8 foo bar ->
                        try Stdout.line! "BadUtf8: $(Inspect.toStr foo) $(Inspect.toStr bar)"
                        Err e

            Ok s ->
                Ok s
jan@framey:~/_code/JanCVanB/bug-repros/mixed-errs-2$ roc check

── TYPE MISMATCH in main.roc ───────────────────────────────────────────────────

This returns something that's incompatible with the return type of the
enclosing function:

16│           when result is
17│               Err e ->
18│                   when e is
19│                       BadUtf8 foo bar ->
20│>                          try Stdout.line! "BadUtf8: $(Inspect.toStr foo) $(Inspect.toStr bar)"
21│                           Err e
22│
23│               Ok s ->
24│                   Ok s

This returns an Err of type:

    [Err [StdoutErr [
        AlreadyExists,
        BrokenPipe,
        Interrupted,
        NotFound,
        Other Str,
        OutOfMemory,
        PermissionDenied,
        Unsupported,
    ]], …]

But I expected the function to have return type:

    [Err [BadUtf8 val val], …] where val implements Inspect, val implements Inspect

────────────────────────────────────────────────────────────────────────────────

1 error and 0 warnings found in 25 ms.

jan@framey:~/_code/JanCVanB/bug-repros/mixed-errs-2$

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:46):

Yeah, this is a more nuanced issue

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:46):

Essentially to be used in the when e is block, e must be either FileReadErr path io_err or FileReadUtf8Err path io_err

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:47):

This is a closed tag due to the when

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:47):

e is then returned

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:47):

Suddenly, that closed tag is being merged with [StderrErr ...] due to the try StdErr.line

view this post on Zulip jan kili (Jan 02 2025 at 15:48):

Ayyy, nice, so adding _ -> Err e seems to fix it.

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:49):

I think you could add a _ -> crash "unreachable" to your when e is to fix it.

That or instead of returning e, you need to return a new error.

                    FileReadErr path io_err ->
                        try Stderr.line! "📰 ❗ Failed to read file $(Path.display path) because $(Inspect.toStr io_err)"
                        try Stderr.line! tip_for_calling_this_app
-                       Err e
+                       Err (FileReadErr path io_err)

view this post on Zulip jan kili (Jan 02 2025 at 15:50):

Ooh that's nicer - especially since in real app functions this pattern is usually combined with error customization

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:50):

This is an interesting sharp edge try/? when combined with when err is

view this post on Zulip jan kili (Jan 02 2025 at 15:51):

Yeah, because I'm in a function that returns a Result, when I see the typechecker message say "this function returns" I don't think "oh yeah when is a function that returns a sometimes-closed union"

view this post on Zulip jan kili (Jan 02 2025 at 15:52):

I read those typechecker messages as referring to the enclosing function's Result Err, but they're not.

view this post on Zulip Notification Bot (Jan 02 2025 at 15:53):

JanCVanB has marked this topic as resolved.

view this post on Zulip jan kili (Jan 02 2025 at 15:54):

I'm happy to continue discussing how to un-sharpen that edge here or elsewhere.

view this post on Zulip jan kili (Jan 02 2025 at 15:55):

Side note - where in the typechecker messages does it indicate a closed tag union? I read all those ...]-ending types as being open.

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:58):

I think this is the only open union cause it is followed by d:

    [Err [
        FileReadErr * *,
        FileReadUtf8Err * *,
    ], …]d

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:59):

That said, any union returned by a function is automatically made open (solves most headaches from open vs closed unions)

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 15:59):

Anyway, sounds like we should at least improve a few error messages here.

view this post on Zulip jan kili (Jan 02 2025 at 16:00):

What does ...] indicate?

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 16:00):

That it left out information cause it was unimportant for the diff

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 16:00):

In this case, it elided the Ok cases

view this post on Zulip jan kili (Jan 02 2025 at 16:01):

Oh, gotcha. Yeah that's outside the Err tag union.

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 16:01):

Eventually we plan to use ... for open unions (and combining unions with ...OtherUnions), but we don't do that yet.

view this post on Zulip jan kili (Jan 02 2025 at 16:02):

Wow, so none of the above Err tags are open, then.

view this post on Zulip jan kili (Jan 02 2025 at 16:03):

That makes sense. I think I was biased toward confusion here because I remember seeing that ...] is the upcoming openness syntax.

view this post on Zulip jan kili (Jan 02 2025 at 16:04):

I should've counted my matching [ ]s more closely!

view this post on Zulip Brendan Hansknecht (Jan 02 2025 at 16:35):

Wow, so none of the above Err tags are open, then

Except that all tags returned from functions are automatically made open. So it is kinda confusing.


Last updated: Jul 06 2025 at 12:14 UTC