Stream: beginners

Topic: combining open unions


view this post on Zulip Jens Petersen (Apr 19 2024 at 03:32):

I don't really understand the table in https://www.roc-lang.org/tutorial#combining-open-unions
or maybe it is just a wording issue perhaps, or I am really not understanding

Surely [Ok Str]* can also include Other field, which would be rejected by [Ok Str] -> Bool, no?
I think the second column means "can receive" Ok Str (eg Ok "hi") from an [Ok Str]* perhaps?

view this post on Zulip Brendan Hansknecht (Apr 19 2024 at 04:59):

I think the confusion here is about what open means. Opens means that it can be merged with something else. So, if a function returns [Ok Str]*, it would never be Other field. It could merge with something that generates Other field, but it couldn't possibly be returned from the original function with the return type of [Ok Str]*.

view this post on Zulip Brendan Hansknecht (Apr 19 2024 at 05:00):

* in general is a bit confusing. It is so unconstrained that it can't contain any useful information.

view this post on Zulip Brendan Hansknecht (Apr 19 2024 at 05:01):

[Ok Str]a is a different story that probably works how you currently think [Ok Str]* works.

view this post on Zulip Luke Boswell (Apr 19 2024 at 05:50):

Also, I think there is a bug currently. Tag unions are meant to be open by default in the return position. Sometimes I have to add an underscore to make this work correctly, so I'll write it out like this thing: Task {} [Tag1, Tag2]_

view this post on Zulip Jens Petersen (Apr 20 2024 at 06:25):

But it is not in the return position in the examples: it is being received, that is my point

view this post on Zulip Brendan Hansknecht (Apr 20 2024 at 15:17):

Yeah, this is not related to returned tags being open

view this post on Zulip Brendan Hansknecht (Apr 20 2024 at 15:19):

Let me try to write out some examples to explain better.

view this post on Zulip Brendan Hansknecht (Apr 20 2024 at 15:35):

Hopefully a decent explanation of why [Ok Str]* can be passed to [Ok Str] -> Bool

view this post on Zulip timotree (Apr 20 2024 at 17:06):

I think Jens has a point here. [Ok Str]* can't always be passed to [Ok Str] -> Bool. In particular, if the value of type [Ok Str]* is a function argument, then it really could contain Other x. Example:

acceptsClosed : [Ok Str] -> {}
acceptsClosed = \_ -> {}

acceptsOpen : [Ok Str]* -> {}
acceptsOpen = \openParameter ->
    acceptsClosed openParameter # ERROR (mismatch between [...]* and [...])

returnsOpen : {} -> [Ok Str]*
returnsOpen = \{} -> Ok "foo"

passesOpen : {} -> {}
passesOpen = \{} ->
    openLocalVariable = returnsOpen {}
    acceptsClosed openLocalVariable # no error...

view this post on Zulip timotree (Apr 20 2024 at 17:27):

In fact, if I add a type annotation openLocalVariable : [Ok Str]*, then both examples error

passesOpenAnnotated : {} -> {}
passesOpenAnnotated = \{} ->
    openLocalVariable : [Ok Str]*
    openLocalVariable = returnsOpen {}
    acceptsClosed openLocalVariable # ERROR (mismatch between [...]* and [...])

view this post on Zulip timotree (Apr 20 2024 at 17:30):

I think it might be helpful to distinguish between [Ok Str]* and [Ok Str]_ here. If I write openLocalVariable : [Ok Str]_ then there's no error, because _ can be chosen to be [], and [Ok Str][] is [Ok Str], which makes the whole thing type correct.

view this post on Zulip timotree (Apr 20 2024 at 17:31):

It would be an accurate statement to say "If I have a local of type [Ok Str]_, it can be passed to a function of type [Ok Str] -> Bool" But I don't think the original statement is accurate

view this post on Zulip timotree (Apr 20 2024 at 17:35):

It seems that in general in Roc, when you're defining a top-level function, * represents a placeholder instantiated by each of your callers, whereas _ represents a placeholder which you get to instantiate.

view this post on Zulip Brendan Hansknecht (Apr 20 2024 at 17:47):

In fact, if I add a type annotation openLocalVariable : [Ok Str]*, then both examples error

Yeah, I'm not sure if that is a bug or not. It definitely is confusing. Realized that early and started #compiler development > Is this a bug?.

"If I have a local of type [Ok Str]_, it can be passed to a function of type [Ok Str] -> Bool"

Need to add a caveat that the _ needs to not merge with anything else for that to work.

Eg

generateOpenOkStr : Str -> [Ok Str]*
generateOpenOkStr = \s ->
    Ok s

restrictsToClosed : [Ok Str] -> Bool
restrictsToClosed = \Ok s->
    !(Str.isEmpty s)

restrictsToDifferentClosed : [Ok Str, Other] -> Bool
restrictsToDifferentClosed = \union->
    when union is
        Ok s -> !(Str.isEmpty s)
        Other -> Bool.false

expect
    in : [Ok Str]_
    in = generateOpenOkStr ""
    out0 = restrictsToDifferentClosed in
    out1 = restrictsToClosed in # Type error: `[Other, …]` vs `[…]`
    out0 == out1

Last updated: Jul 06 2025 at 12:14 UTC