Stream: beginners

Topic: Pattern matching on booleans


view this post on Zulip Chris Duncan (Oct 09 2022 at 21:04):

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?

view this post on Zulip jan kili (Oct 09 2022 at 21:07):

I believe that's an upcoming feature/implementation!

view this post on Zulip Chris Duncan (Oct 09 2022 at 21:09):

Nice! Is there a Zulip topic or a GitHub issue I can follow?

view this post on Zulip jan kili (Oct 09 2022 at 21:13):

I'm not sure, but I can find when I was told this

view this post on Zulip jan kili (Oct 09 2022 at 21:16):

https://roc.zulipchat.com/#narrow/stream/231634-beginners/topic/Am.20I.20doing.20something.20silly.3F/near/302380580

view this post on Zulip Brian Carroll (Oct 10 2022 at 06:21):

You can extract the Boolean variable out of the pattern match, and then do a separate if expression in that branch.

view this post on Zulip Brian Carroll (Oct 10 2022 at 06:26):

foo = \value ->
  when value is
    Missing -> Present Bool.false
    Present x -> if x then Present Bool.true else Missing

view this post on Zulip Joshua Warner (Feb 07 2023 at 02:53):

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!

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:08):

it's a great point! I updated the description based on Ayaz's comment

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:08):

I think there's a pretty clear answer in this case :big_smile:

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:27):

That seems like quite a shame that booleans are not considered exhaustive!

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:34):

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]

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:36):

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:

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:36):

bools used to be defined exactly that way!

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:36):

here's the background on why we ended up changing it: https://docs.google.com/document/d/1a51n7eIEbPjCWnGaL-pWbZBsRfi55GVQwIdPQTUu49Q/edit

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:37):

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.

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:38):

an option we always have is to make true and false be language keywords

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:38):

which desugar to Bool.true and Bool.false, plus have special exhaustiveness checking properties

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:39):

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!

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:40):

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

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:40):

I feel like there's maybe also room for "opaque type, but with some 'exhaustive' annotations sprinkled around"...

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:41):

:thinking: hm, at that point what would be the difference between it just not being opaque?

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:41):

I guess that it's nominal

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:42):

Yeah; it avoids the downsides outlined in the doc you linked

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:42):

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...)

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:42):

interesting, although I think for a language feature like that we'd want more motivating use cases than just Bool :big_smile:

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:43):

Fair :)

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:43):

Could be something _only_ allowed in the standard library for now

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:43):

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

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:44):

True true

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:44):

pun intended? :grinning_face_with_smiling_eyes:

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:45):

Haha actually no - but that would have been good

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:48):

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?

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:49):

you can!

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:49):

you can already qualify with the current module

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:49):

e.g. if I'm in Foo I can write Foo.bar

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:49):

Oh wow

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:49):

that was actually designed to be supported exactly for this

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:49):

back in 2019 haha

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:50):

not for bool - it was that I always wanted to pattern match on constants like HTTP status codes

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:50):

in other languages

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:50):

and it was always impossible because in the current module they're lowercase and therefore interpreted as identifiers in patterns

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:50):

Rust solves this by requiring constnats to be SCREAMING_SNAKE_CASE but I don't want to require that for obvious reasons :laughing:

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:51):

Rust isn't totally immune - if you don't have a constant named SCREAMING_SNAKE_CASE in scope, it becomes a bound variable

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:51):

I got bit by that in some production code a while back

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:52):

ouch

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:52):

well this would be immune to that fortunately haha!

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:52):

Anyway; I like this design

view this post on Zulip Joshua Warner (Feb 07 2023 at 03:52):

:hat_tip:

view this post on Zulip Richard Feldman (Feb 07 2023 at 03:54):

awesome! :smiley:

view this post on Zulip Luke Boswell (Feb 07 2023 at 07:28):

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"

view this post on Zulip Brian Carroll (Feb 07 2023 at 10:52):

Sounds like it's intended to work, yes. What are you seeing?

view this post on Zulip Richard Feldman (Feb 07 2023 at 13:03):

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