Stream: ideas

Topic: expose Bool.true as True


view this post on Zulip Matthias Toepp (May 10 2023 at 02:53):

it seems awkward and unusual to have Bool.true and Bool.false. Can we expose that as just true/false?

view this post on Zulip Luke Boswell (May 10 2023 at 03:01):

I think it was like that until recently and we changed it to be an opaque. I think there is a thread or two on it, I'll try and dig it out.

view this post on Zulip Luke Boswell (May 10 2023 at 03:04):

Yeah pretty sure this zulip discussion has more detail.

view this post on Zulip Matthias Toepp (May 10 2023 at 03:04):

thanks

view this post on Zulip Luke Boswell (May 10 2023 at 03:05):

Actually, I think the next step Richard mentioned if Bool.true is too cumbersome then true would be considered an option.

view this post on Zulip Matthias Toepp (May 10 2023 at 03:18):

but i actually want to pattern match on bools so I'm screwed anyway. :expressionless: even if it was exposed. (I give up).

view this post on Zulip Agus Zubiaga (May 10 2023 at 03:20):

You can use if guards in when for that

view this post on Zulip Matthias Toepp (May 10 2023 at 03:22):

i would like to be able to do this:

size = when x > 5 is
    True -> "greater"
    False -> "smaller"

view this post on Zulip Matthias Toepp (May 10 2023 at 03:24):

and have it look nice, i.e. without extra syntax noise.

view this post on Zulip Matthias Toepp (May 10 2023 at 03:29):

...for this purpose:
https://roc.zulipchat.com/#narrow/stream/304641-ideas/topic/pattern.20matching.20if

view this post on Zulip Brendan Hansknecht (May 10 2023 at 03:50):

Oh, interesting, didn't realize that opaque bool blocks:

size = when x > 5 is
    Bool.true -> "greater"
    Bool.false -> "smaller"

view this post on Zulip Georges Boris (May 10 2023 at 08:38):

I just noticed that Roc doesn't have the equivalent of Elm's Basics module - so what gets automatically added to a new module's scope? I understand types like Num * are just tagged unions but we still have access to infix functions, etc.

If we did have a Basics/Prelude it would be great to have it expose true and false automatically - no need to change anything about it being an opaque type, but it would already be a lot less weird.

view this post on Zulip Richard Feldman (May 10 2023 at 10:17):

Brendan Hansknecht said:

Oh, interesting, didn't realize that opaque bool blocks:

size = when x > 5 is
    Bool.true -> "greater"
    Bool.false -> "smaller"

this is supposed to Just Work - I think it just hasn't been implemented yet :big_smile:

view this post on Zulip Richard Feldman (May 10 2023 at 10:18):

pattern matching on qualified constants should parse, and then desugar to:

Bool.true -> "greater" becomes _ if condition == Bool.true -> "greater"

view this post on Zulip Richard Feldman (May 10 2023 at 10:19):

this was a design going back to the very beginning of the language: you can qualify something with the current module (e.g. if I'm in module Foo I can write Foo.blah) so that you can pattern match on constants in that module

view this post on Zulip Richard Feldman (May 10 2023 at 10:19):

it just still hasn't been implemented

view this post on Zulip Brendan Hansknecht (May 10 2023 at 13:44):

Oh, so this isn't due to Bool being opaque? It is just because we can't pattern match on constants.

Does that mean this should work as well

pi = 3.145
e = ...

main =
    ...
    when myFloat is
        pi ->
        e ->
        _ ->
    ...

view this post on Zulip Brendan Hansknecht (May 10 2023 at 13:46):

@Matthias Toepp One thing I just remember, with the current system (once what richard mentioned above is fixed), you should be able to get:

size = when x > 5 is
    true -> "greater"
    false -> "smaller"

You just have to add:

true = Bool.true
false = Bool.false

to your module

view this post on Zulip Brendan Hansknecht (May 10 2023 at 13:55):

pattern matching on qualified constants should parse, and then desugar to:

Bool.true -> "greater" becomes _ if condition == Bool.true -> "greater"

Isn't this getting rid of the advantage of knowing Bool.true at compile time? As in, doesn't when currently turn into a smarter decision structure that can be converted into jump table? With a bunch of individual if branches, I think that will be lost. So we may want to convert this differently in practice (at least for compile time known values)

view this post on Zulip Richard Feldman (May 10 2023 at 14:39):

Brendan Hansknecht said:

Does that mean this should work as well

pi = 3.145
e = ...

main =
    ...
    when myFloat is
        pi ->
        e ->
        _ ->
    ...

kinda, except it would need to be MyModule.pi and MyModule.e because as currently written, those are pattern variables because they're lowercase identifiers :big_smile:

view this post on Zulip Richard Feldman (May 10 2023 at 14:39):

but as long as you can refer to them in a qualified way (including being able to qualify them the name of the module you're in right now) then it's unambiguous

view this post on Zulip Richard Feldman (May 10 2023 at 14:41):

regarding the idea of exposing true and false - see the original proposal under the sections "Fully-Qualified true and false" and "Future Optionality"

view this post on Zulip Richard Feldman (May 10 2023 at 14:43):

Brendan Hansknecht said:

pattern matching on qualified constants should parse, and then desugar to:

Bool.true -> "greater" becomes _ if condition == Bool.true -> "greater"

Isn't this getting rid of the advantage of knowing Bool.true at compile time? As in, doesn't when currently turn into a smarter decision structure that can be converted into jump table? With a bunch of individual if branches, I think that will be lost. So we may want to convert this differently in practice (at least for compile time known values)

yeah, I guess I should say it desugars to the equivalent of that; of course, we can compile to something more efficient than literally that! :big_smile:

view this post on Zulip Matthias Toepp (May 10 2023 at 14:48):

It seems like it would be strange to expose Bool.true as true and then not be able to pattern match on it.

view this post on Zulip Richard Feldman (May 10 2023 at 14:49):

yeah - I think if we wanted to go that route, we would have true be a reserved language keyword like it is in C, JavaScript, Java, etc.

view this post on Zulip Richard Feldman (May 10 2023 at 14:50):

but as mentioned in the document, a downside of doing that is that it encourages more use of Bool compared to tags, which I think is the opposite of the incentive we want

view this post on Zulip Richard Feldman (May 10 2023 at 14:50):

we should encourage using tags over Bool by default

view this post on Zulip Matthias Toepp (May 10 2023 at 14:52):

Would it be perhaps valuable to have the functionality anyway to be able to expose Opaque Tags. So there would be a way to say True in this context refers to the opaque Bool.true (but maybe that would be desirable for more than just that).

view this post on Zulip Matthias Toepp (May 10 2023 at 15:02):

anyway I get the idea.

view this post on Zulip Brendan Hansknecht (May 10 2023 at 15:27):

Definitely a bit weird that you could do this:

# In some func
when myFloat is
    Math.pi ->
    Math.e ->
    _ ->

But not this:

# Top level constants
pi = Math.pi
e = Math.e

# In some func
when myFloat is
    pi ->
    e ->
    _ ->

view this post on Zulip Brendan Hansknecht (May 10 2023 at 15:28):

But I do get the syntax conflict here.

view this post on Zulip Brendan Hansknecht (May 10 2023 at 15:29):

It would be the same as doing

when something is
    Ok v ->
    x -> # x binds to something.

view this post on Zulip Matthias Toepp (May 10 2023 at 15:29):

erlang has the pin operator that solves that. Saying ^pi means you are not creating a new pattern variable but referring to the value of pi.

view this post on Zulip Matthias Toepp (May 10 2023 at 15:31):

(in elixir as well)

view this post on Zulip Allan Clark (May 10 2023 at 15:40):

Brendan Hansknecht said:

Definitely a bit weird that you could do this:

Honestly I don't find that weird. I think that's a good constraint. In your second example a common error might be to do:

# Top level constants
pi = Math.pi
e = Math.e

# In some func
when myFloat is
    pie  ->
    e ->
    _ ->

In this case, you would get a warning about two redundant patterns, but what if the pie was part of a larger pattern e.g.

# Top level constants
pi = Math.pi
e = Math.e

# In some func
when myFun myFloat is
    (pie, Monday) ->
    (e, Tuesday) ->
    _ ->

But by forcing the qualified name you would have to write:

# In some func
when myFun myFloat is
    (Math.pie, Monday)  ->
    (Math.e, Tuesday) ->
    _ ->

And you would get an error that Math.pie doesn't exist.

view this post on Zulip Brendan Hansknecht (May 10 2023 at 16:03):

Very good point

view this post on Zulip Matthias Toepp (May 10 2023 at 16:06):

I'm trying to understand the document that RF linked to about the opaque bool proposal: the original proposal

view this post on Zulip Matthias Toepp (May 10 2023 at 16:07):

Why is this thinking wrong...

view this post on Zulip Matthias Toepp (May 10 2023 at 16:11):

It seems that we could/should rely on and lean on those that are writing formatters and encoders for roc to get it right, before we change anything in the language itself to accomodate the posibility that they get it wrong? I'm really ignorant here, I'm just curious.

view this post on Zulip Richard Feldman (May 10 2023 at 16:15):

the problem is that the formatting/encoding authors wouldn't have enough information to "get it right"

view this post on Zulip Richard Feldman (May 10 2023 at 16:15):

if you receive a True tag, how do you know it's supposed to represent a boolean? Whoever is using your formatting code might have put True in a union like [True, Green, Purple]

view this post on Zulip Richard Feldman (May 10 2023 at 16:17):

so either they decide to always encode True as a tag union (e.g. the JSON formatter would never be able to encode the JSON value true because it would always be encoding a tag union representation) or else they decide to always encode True as a boolean, in which case they might incorrectly encode it if someone gives them a tag union that happens to include True

view this post on Zulip Richard Feldman (May 10 2023 at 16:18):

as noted in the document, a possible design here is to have a convention of "look just don't put True in a tag union other than Bool, c'mon" but this is kind of the exact situation where opaque types make more sense than tag unions

view this post on Zulip Richard Feldman (May 10 2023 at 16:18):

instead of having a convention that people can get wrong, you have a clearly-defined nominal type with unambiguous semantics in all the relevant cases here

view this post on Zulip Matthias Toepp (May 10 2023 at 16:20):

(thinking...)

view this post on Zulip Ayaz Hafiz (May 10 2023 at 16:27):

The roc compiler could verify that encoders/decoders only generate the "right thing" for a union like [True, False], since the compiler controls the generation of encoders/decoders for structural types (and any other ability can only be specified manually for an opaque type). So, the compiler could make sure that JSON encoding/decoding of true/false only applies to [True, False], and it would not try to do the same for [True, Green, Purple]. So I don't think that impacts the boolean opaque/structural type distinction, unless Im missing something?

view this post on Zulip Richard Feldman (May 10 2023 at 16:29):

hm, I thought we'd talked about that at the time and there was a reason it was more complicated than that

view this post on Zulip Richard Feldman (May 10 2023 at 16:29):

but I don't remember why :big_smile:

view this post on Zulip Richard Feldman (May 10 2023 at 16:33):

oh right, I remember

view this post on Zulip Richard Feldman (May 10 2023 at 16:33):

if you pass it True, that doesn't have the type [True, False] it has the type [True]

view this post on Zulip Bryce Miller (May 10 2023 at 16:34):

Even if it were feasible, having to explain why the tag union [True, False] is treated differently than other tag unions seems leas than ideal.

“It’s literally exactly the same as any other tag union, except it’s sometimes not. But it’s fundamentally the same. But sometimes different.”

view this post on Zulip Matthias Toepp (May 10 2023 at 16:41):

I don't know anything about formatters and encoding, but isn't there something strange about changing the language to accommodate an apparent inability to transfer the information that the compiler knows. The compiler presumably would be able to distinguish between tags of the Bool type and other True tags that someone creates.

view this post on Zulip Matthias Toepp (May 10 2023 at 16:46):

Obviously its a complex issue that I know nothing about, and you guys have thought deeply about it, so I trust your judgment, it just feels like something is not right there.

view this post on Zulip Ayaz Hafiz (May 10 2023 at 16:47):

Right now tags are just strings - they are nominal only in their name, and don't preserve names about the module they come from or anything - that's intentional for the purposes of being able to check "does this A tag from module Foo equal this other tag A from module Bar"? If we scoped tag names to modules, they begin to resemble the value that opaque types provide. Does that help clarify concern re. the Bool type/other True tag someone creates?

view this post on Zulip Matthias Toepp (May 10 2023 at 16:51):

@Ayaz Hafiz yes that helps.

view this post on Zulip Matthias Toepp (May 10 2023 at 16:52):

Thanks all of you, that sheds a lot of light on things. I appreciate you guys taking the time to explain all that!!!

view this post on Zulip Matthias Toepp (May 13 2023 at 13:29):

Two questions :blush: related to how this was in the original design using non-opaque True and False...

view this post on Zulip Matthias Toepp (May 13 2023 at 13:30):

view this post on Zulip Matthias Toepp (May 13 2023 at 13:31):

... in that world, the ambiguity issue with encoding would not exist. Is that correct?

view this post on Zulip Brendan Hansknecht (May 13 2023 at 15:35):

Was the type Bool automatically in scope so that a person could indicate a value was of the type Bool without creating the Bool type?

Bool used to be defined as Bool : [True, False] in the Bool module.
Just like today you could just use Bool instead of Bool.Bool
Given it was just a regular tag, there was no need to explicitly import True or False.

So the old code would look like:

x : Bool
x = True

if it was a clear convention that only values that are known to the compiler to be of type Bool would be encoded as bools.

Encoding didn't exist back then. In fact, abilities didn't exist at all. They were just an idea.
If we had tried to make encoding back then, it would have need to be treated specially.
Otherwise, it would have just been encoded like any other tag.

If people could always be relied upon to provide clarifying type signatures when they desired a type of Bool to be encoded but the compiler would have actually inferred [True]* or [False]*...
... in that world, the ambiguity issue with encoding would not exist. Is that correct?

If we added special handling to the compiler for [True, False] such that it used a separate encode function from the rest of the tag unions, adding a type should fix this issue. Though there are probably some other minor details related to handling other unions that could contain a True tag.

view this post on Zulip Richard Feldman (May 13 2023 at 15:59):

If we added special handling to the compiler for [True, False] such that it used a separate encode function from the rest of the tag unions, adding a type should fix this issue. Though there are probably some other minor details related to handling other unions that could contain a True tag.

most notably the tag True itself, e.g. if you did Encode.encode True, encode would see the tag [True], not the tag [True, False]

view this post on Zulip Brendan Hansknecht (May 13 2023 at 16:02):

Sure but when do you encode just True?

view this post on Zulip Brendan Hansknecht (May 13 2023 at 16:02):

Sounds like a niche use case

view this post on Zulip Brendan Hansknecht (May 13 2023 at 16:02):

Generally you encode a struct or tuple which you can type

view this post on Zulip Brendan Hansknecht (May 13 2023 at 16:03):

If you were just encoding a constant by itself, probably could hardcore the string it encodes into.

view this post on Zulip Richard Feldman (May 13 2023 at 16:03):

sure, but that kind of exacerbates the problem - it's an edge case, so it's easier to not think about what will happen in that case and have something do the wrong thing

view this post on Zulip Richard Feldman (May 13 2023 at 16:03):

the big benefit of opaque Bool is that it makes the semantics actually clear in all the edge cases

view this post on Zulip Richard Feldman (May 13 2023 at 16:03):

(the big benefit when it comes to things like encoding and decoding)

view this post on Zulip Brendan Hansknecht (May 13 2023 at 16:05):

:+1:

view this post on Zulip Matthias Toepp (May 13 2023 at 16:29):

You would need to do something like Encode.encode (True : Bool) or the equivalent in order to get the equivalent of Encode.encode Bool.true.

view this post on Zulip Matthias Toepp (May 13 2023 at 16:32):

Is it implied that there is more special treatment required to encode a non-opaque Bool type than an opaque one?

view this post on Zulip Matthias Toepp (May 13 2023 at 16:33):

To my ignorant mind I just imagine (roughly equal?) special treatment in either case.

view this post on Zulip Brendan Hansknecht (May 13 2023 at 16:35):

To not introduce a new syntax, it would be:

const : Bool
const = True
Encode.encode const

view this post on Zulip Brendan Hansknecht (May 13 2023 at 16:37):

Is it implied that there is more special treatment required to encode a non-opaque Bool type than an opaque one?

I believe that answer is yes, or should be yes. That said, I am not 100% sure due to it still require some form of compiler work for auto derive.

view this post on Zulip Matthias Toepp (May 13 2023 at 16:42):

@Brendan Hansknecht So if i understand correctly, your answer to both questions is yes. But there would probably be more special compiler work then now and it would be awkward since we need to write types on a separate line with the current syntax.

view this post on Zulip Matthias Toepp (May 13 2023 at 16:44):

(overlooking the hazard that someone might forget to add the type annotation)

view this post on Zulip Matthias Toepp (May 13 2023 at 16:51):

Is it not concerning that the same challenge could come up with any custom type?

Color : [Red, Green, Blue]
Encode.encode Red        # oops, Red wasn't encoded as a Color.

view this post on Zulip Matthias Toepp (May 13 2023 at 17:05):

maybe there should be a qualified way to write that? If there isn't already.

Encode.encode Color::Red

view this post on Zulip Matthias Toepp (May 13 2023 at 17:11):

It seems like that would be more important to have than in Rust.

view this post on Zulip Brendan Hansknecht (May 13 2023 at 17:15):

To my current understanding, Red would just encode as "Red" or whatever the string format is. If you want a more controlled encode, you need an opaque type.

view this post on Zulip Brendan Hansknecht (May 13 2023 at 17:15):

So it isn't tied to Color at all

view this post on Zulip Brendan Hansknecht (May 13 2023 at 17:15):

This doesnt work for Bool because you don't want "True"

view this post on Zulip Brendan Hansknecht (May 13 2023 at 17:16):

You want true

view this post on Zulip Matthias Toepp (May 13 2023 at 17:16):

Yes exactly. So its the same hazzard as with bools

view this post on Zulip Brendan Hansknecht (May 13 2023 at 17:16):

I dont see the hazzard

view this post on Zulip Brendan Hansknecht (May 13 2023 at 17:17):

Encode.encode Red and Encode.encode Color::Red would generate the same thing.

view this post on Zulip Brendan Hansknecht (May 13 2023 at 17:17):

Unlike with Bool where it needs to generate a special value.

view this post on Zulip Brendan Hansknecht (May 13 2023 at 17:18):

Anything that needs to generate a special value in the current scheme must be opaque.

view this post on Zulip Brendan Hansknecht (May 13 2023 at 17:19):

That is how encode is built to work. If you need special handling different from what is auto derived, you must make the value opaque.

view this post on Zulip Brendan Hansknecht (May 13 2023 at 17:20):

Then, since it is opaque, you can make the type implement the Encode ability

view this post on Zulip Brendan Hansknecht (May 13 2023 at 17:20):

That will give it the special encoding.

view this post on Zulip Matthias Toepp (May 13 2023 at 17:24):

I was suggesting that if one writes Color::Red that that would differentiate it from Color [Red]*.

view this post on Zulip Matthias Toepp (May 13 2023 at 17:26):

Isn't a closed set in roc comparable to an ADT in Haskell?

view this post on Zulip Brendan Hansknecht (May 13 2023 at 17:28):

I was suggesting that if one writes Color::Red that that would differentiate it from Color.

Can you elaborate on this point?

view this post on Zulip Matthias Toepp (May 13 2023 at 17:29):

Yes... I'm assuming that one would want to use a closed set in roc the way one would use an ADT in Haskell?

view this post on Zulip Matthias Toepp (May 13 2023 at 17:30):

I was trying to indicate (and encode) that Color::Red is a member of the closed set of colors and not just a [Red]*

view this post on Zulip Brendan Hansknecht (May 13 2023 at 17:33):

Sure, but would that encode differently in practice is the question. I think in formats like json or xml the answer is no. Definitely in a form like protobuf this could lead to issues (where you would prefer to encode enums as just numbers).

Also, I am not versed enough to talk about the types compared to haskell and be confident in my answers.

view this post on Zulip Matthias Toepp (May 13 2023 at 17:34):

Brendan Hansknecht said:

I was suggesting that if one writes Color::Red that that would differentiate it from Color.

Can you elaborate on this point?

I corrected that: Color ==> [Red]* (sorry)

view this post on Zulip Matthias Toepp (May 13 2023 at 17:39):

It guess I'm saying that it might be valuable to be able to encode the distinction between a value of a named closed union compared to the value of an open union. But that's me coming from never having used open unions before!

view this post on Zulip Notification Bot (May 13 2023 at 17:49):

5 messages were moved from this topic to #ideas > encoding tag union indices by Richard Feldman.

view this post on Zulip Matthias Toepp (May 15 2023 at 12:13):

In case it has any value...my take away from this conversation is that beyond what is discussed in the original opaque bool proposal, there may be another possibility. A possible solution seems to be to special case the Bool type [True,False] (closed union) for encoding as bools instead of the opaque Bool.true/Bool.false and have the general rule that when encoding a tag, a type annotation is required by the compiler when a closed union tag with the same name is in scope

That scheme, it seems, would make it more intuitive for newcomers to use True and False when not encoding a value. Later, when they try to encode a bool (True lets say) they might be annoyed at being prompted by the compiler to add a type annotation since it does not know which of [True]* or True : Bool the user desires to encode...but the inconvenience could be learning/practicing a more general truth: when encoding a tag, a type annotation must be provided if a closed union tag with the same name is in scope (to distinguish between the open and closed union tags).

If that's another possible solution to the ambiguous encoding problem, then perhaps that could at least be added to the document as one of the possible future directions.

I understand that there is not a desire to move away from the opaque bool solution until moving away would be necessitated. I also understand that providing friction by requiring people to write something more verbose (Bool.true over True) may be desirable.

I hope not much time is spent in further discussion.


Last updated: Jun 16 2026 at 16:19 UTC