I went down a rabbit hole trying to pattern match on booleans. I'm trying to implement a function that satisfies this type:
foo : [Missing, Present Bool] -> [Missing, Present Bool]
I started by doing the following:
foo = \value ->
when value is
Missing -> Present Bool.false
Present Bool.false -> Present Bool.true
Present Bool.true -> Missing
foo Missing
I'm not surprised that I got the following error in hindsight since Bool.false
is a value, not a tag:
── SYNTAX PROBLEM ──────────────────────────────────────────────────────────────
This pattern is malformed:
8│ Present Bool.false -> Present Bool.true
^^^^^^^^^^
It would be great to see a suggestion here on how to remedy this. Without guidance, I tried the next thing:
foo = \value ->
when value is
Missing -> Present False
Present False -> Present True
Present True -> Missing
foo Missing
With the following error, I discover that Bool
is an opaque type, so I can't use their tags in my module:
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
The branches of this when expression don't match the condition:
6│> when value is
7│ Missing -> Present False
8│ Present False -> Present True
9│ Present True -> Missing
This value value is a:
[Missing, Present Bool]
But the branch patterns have type:
[Missing, Present [False, True]]
The branches must be cases of the when condition's type!
It would be great to see some guidance here on the proper way to pattern match booleans. I remembered that I can use if
expressions in the pattern match, so I tried the following:
foo = \possibleValue ->
when possibleValue is
Missing -> Present Bool.false
Present value if Bool.not value -> Present Bool.true
Present value if value -> Missing
foo Missing
The error this time surprised me:
── UNSAFE PATTERN ──────────────────────────────────────────────────────────────
This when does not cover all the possibilities:
6│> when possibleValue is
7│> Missing -> Present Bool.false
8│> Present value if Bool.not value -> Present Bool.true
9│> Present value if value -> Missing
Other possibilities include:
Present _ (note the lack of an if clause)
I would have to crash if I saw one of those! Add branches for them!
Logically, all the cases are covered, so I wonder if the compiler can't possibly determine that this would work in the general case.
Is there a way that I can pattern match on booleans, or will I have to follow the compilers advice and just ignore the content of Present
in the third branch?
I believe that's an upcoming feature/implementation!
Nice! Is there a Zulip topic or a GitHub issue I can follow?
I'm not sure, but I can find when I was told this
You can extract the Boolean variable out of the pattern match, and then do a separate if
expression in that branch.
foo = \value ->
when value is
Missing -> Present Bool.false
Present x -> if x then Present Bool.true else Missing
Just ran into this. Looks like it's tracked here: https://github.com/roc-lang/roc/issues/4152
@Richard Feldman do you have any thoughts following @Ayaz Hafiz 's comment there?
This (or something like it) would be really nice to have!
it's a great point! I updated the description based on Ayaz's comment
I think there's a pretty clear answer in this case :big_smile:
That seems like quite a shame that booleans are not considered exhaustive!
Why are bools behind an opaque type in the first place? It would seem obvious to me that you'd want bool to just be Bool : [True, False]
Joshua Warner said:
That seems like quite a shame that booleans are not considered exhaustive!
I think that's true in the abstract sense, but I've spent a lot of time writing with languages that support this and the total number of times I can recall it ever coming up is 0 :big_smile:
bools used to be defined exactly that way!
here's the background on why we ended up changing it: https://docs.google.com/document/d/1a51n7eIEbPjCWnGaL-pWbZBsRfi55GVQwIdPQTUu49Q/edit
This is the but I've spent a lot of time writing with languages that support this and the total number of times I can recall it ever coming up is 0 :big_smile:
Interesting! But judging from this thread, I'm the third person here to be surprised by this.
an option we always have is to make true
and false
be language keywords
which desugar to Bool.true
and Bool.false
, plus have special exhaustiveness checking properties
but as noted in that document, I'd rather continue running the experiment of fully-qualified before considering that - because we all know what true
and false
feel like, but there's no precedent I'm aware of for what Bool.true
and Bool.false
feel like!
this is definitely a point worth considering in that analysis, but I don't think it crosses the line of "ok, experiment over, let's go with true
and false
keywords" at this point
I feel like there's maybe also room for "opaque type, but with some 'exhaustive' annotations sprinkled around"...
:thinking: hm, at that point what would be the difference between it just not being opaque?
I guess that it's nominal
Yeah; it avoids the downsides outlined in the doc you linked
Something like:
## The boolean true value.
true : Bool
true = @Bool True
## The boolean false value.
false : Bool
false = @Bool False
@require_exhaustive (Bool.true, Bool.false)
(syntax pending some bikeshedding...)
interesting, although I think for a language feature like that we'd want more motivating use cases than just Bool
:big_smile:
Fair :)
Could be something _only_ allowed in the standard library for now
I think that could be confusing though - fully-qualified pattern matching works one way for every other type, and then a different way for Bool
True true
pun intended? :grinning_face_with_smiling_eyes:
Haha actually no - but that would have been good
Ok - so on the assumption that we've implemented pattern matching on qualified constants, one thing that would seem weird to me is: why can I not match on constants in the current module?
you can!
you can already qualify with the current module
e.g. if I'm in Foo
I can write Foo.bar
Oh wow
that was actually designed to be supported exactly for this
back in 2019 haha
not for bool - it was that I always wanted to pattern match on constants like HTTP status codes
in other languages
and it was always impossible because in the current module they're lowercase and therefore interpreted as identifiers in patterns
Rust solves this by requiring constnats to be SCREAMING_SNAKE_CASE
but I don't want to require that for obvious reasons :laughing:
Rust isn't totally immune - if you don't have a constant named SCREAMING_SNAKE_CASE
in scope, it becomes a bound variable
I got bit by that in some production code a while back
ouch
well this would be immune to that fortunately haha!
Anyway; I like this design
:hat_tip:
awesome! :smiley:
why can I not match on constants in the current module?
Should this work? I think I misunderstood this feature.
interface Thing exposes [] imports []
x = 3
y = 4
abc = \i ->
when i is
Thing.x -> "Three"
Thing.y -> "Four"
_ -> "Other"
expect abc 3 == "Three"
Sounds like it's intended to work, yes. What are you seeing?
that's the design, but pattern matching on qualified things hasn't been implemented yet :big_smile:
Last updated: Jul 05 2025 at 12:14 UTC