it seems awkward and unusual to have Bool.true and Bool.false. Can we expose that as just true/false?
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.
Yeah pretty sure this zulip discussion has more detail.
thanks
Actually, I think the next step Richard mentioned if Bool.true is too cumbersome then true would be considered an option.
but i actually want to pattern match on bools so I'm screwed anyway. :expressionless: even if it was exposed. (I give up).
You can use if guards in when for that
i would like to be able to do this:
size = when x > 5 is
True -> "greater"
False -> "smaller"
and have it look nice, i.e. without extra syntax noise.
...for this purpose:
https://roc.zulipchat.com/#narrow/stream/304641-ideas/topic/pattern.20matching.20if
Oh, interesting, didn't realize that opaque bool blocks:
size = when x > 5 is
Bool.true -> "greater"
Bool.false -> "smaller"
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.
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:
pattern matching on qualified constants should parse, and then desugar to:
Bool.true -> "greater" becomes _ if condition == Bool.true -> "greater"
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
it just still hasn't been implemented
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 ->
_ ->
...
@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
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)
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:
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
regarding the idea of exposing true and false - see the original proposal under the sections "Fully-Qualified true and false" and "Future Optionality"
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.trueat compile time? As in, doesn'twhencurrently 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:
It seems like it would be strange to expose Bool.true as true and then not be able to pattern match on it.
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.
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
we should encourage using tags over Bool by default
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).
anyway I get the idea.
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 ->
_ ->
But I do get the syntax conflict here.
It would be the same as doing
when something is
Ok v ->
x -> # x binds to something.
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.
(in elixir as well)
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.
Very good point
I'm trying to understand the document that RF linked to about the opaque bool proposal: the original proposal
Why is this thinking wrong...
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.
the problem is that the formatting/encoding authors wouldn't have enough information to "get it right"
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]
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
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
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
(thinking...)
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?
hm, I thought we'd talked about that at the time and there was a reason it was more complicated than that
but I don't remember why :big_smile:
oh right, I remember
if you pass it True, that doesn't have the type [True, False] it has the type [True]
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.”
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.
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.
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?
@Ayaz Hafiz yes that helps.
Thanks all of you, that sheds a lot of light on things. I appreciate you guys taking the time to explain all that!!!
Two questions :blush: related to how this was in the original design using non-opaque True and False...
In the original system...
and
... in that world, the ambiguity issue with encoding would not exist. Is that correct?
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.
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]
Sure but when do you encode just True?
Sounds like a niche use case
Generally you encode a struct or tuple which you can type
If you were just encoding a constant by itself, probably could hardcore the string it encodes into.
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
the big benefit of opaque Bool is that it makes the semantics actually clear in all the edge cases
(the big benefit when it comes to things like encoding and decoding)
:+1:
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.
Is it implied that there is more special treatment required to encode a non-opaque Bool type than an opaque one?
To my ignorant mind I just imagine (roughly equal?) special treatment in either case.
To not introduce a new syntax, it would be:
const : Bool
const = True
Encode.encode const
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.
@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.
(overlooking the hazard that someone might forget to add the type annotation)
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.
maybe there should be a qualified way to write that? If there isn't already.
Encode.encode Color::Red
It seems like that would be more important to have than in Rust.
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.
So it isn't tied to Color at all
This doesnt work for Bool because you don't want "True"
You want true
Yes exactly. So its the same hazzard as with bools
I dont see the hazzard
Encode.encode Red and Encode.encode Color::Red would generate the same thing.
Unlike with Bool where it needs to generate a special value.
Anything that needs to generate a special value in the current scheme must be opaque.
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.
Then, since it is opaque, you can make the type implement the Encode ability
That will give it the special encoding.
I was suggesting that if one writes Color::Red that that would differentiate it from Color [Red]*.
Isn't a closed set in roc comparable to an ADT in Haskell?
I was suggesting that if one writes Color::Red that that would differentiate it from Color.
Can you elaborate on this point?
Yes... I'm assuming that one would want to use a closed set in roc the way one would use an ADT in Haskell?
I was trying to indicate (and encode) that Color::Red is a member of the closed set of colors and not just a [Red]*
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.
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)
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!
5 messages were moved from this topic to #ideas > encoding tag union indices by Richard Feldman.
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