Stream: beginners

Topic: Exhaustiveness with U8s


view this post on Zulip Hannes (Jun 13 2023 at 05:23):

I know very little about compilers and type-checkers, so this might be obvious, but would it be possible for the compiler to check this pattern match for exhaustiveness without the user supplying the default case _ -> crash? I believe a U8 can only contain numbers 0 to 63 and there's no other values like a NaN value like for floats. I admit this is a pretty rare situation, so I understand if it's not in scope, and I imagine it's the kind of thing that could kill compiler performance.

# Convert a byte, represented as a U8 to a list of (big-endian) bits, represented as a list of bools.
byteToBits : U8 -> List Bool
byteToBits = \byte ->
    when byte is
        0 -> [Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false]
        1 -> [Bool.false, Bool.false, Bool.false, Bool.false, Bool.false, Bool.true]
        2 -> [Bool.false, Bool.false, Bool.false, Bool.false, Bool.true, Bool.false]
        3 -> [Bool.false, Bool.false, Bool.false, Bool.false, Bool.true, Bool.true]
        4 -> [Bool.false, Bool.false, Bool.false, Bool.true, Bool.false, Bool.false]
        5 -> [Bool.false, Bool.false, Bool.false, Bool.true, Bool.false, Bool.true]
        6 -> [Bool.false, Bool.false, Bool.false, Bool.true, Bool.true, Bool.false]
        7 -> [Bool.false, Bool.false, Bool.false, Bool.true, Bool.true, Bool.true]
        8 -> [Bool.false, Bool.false, Bool.true, Bool.false, Bool.false, Bool.false]
        9 -> [Bool.false, Bool.false, Bool.true, Bool.false, Bool.false, Bool.true]
        10 -> [Bool.false, Bool.false, Bool.true, Bool.false, Bool.true, Bool.false]
        11 -> [Bool.false, Bool.false, Bool.true, Bool.false, Bool.true, Bool.true]
        12 -> [Bool.false, Bool.false, Bool.true, Bool.true, Bool.false, Bool.false]
        13 -> [Bool.false, Bool.false, Bool.true, Bool.true, Bool.false, Bool.true]
        14 -> [Bool.false, Bool.false, Bool.true, Bool.true, Bool.true, Bool.false]
        15 -> [Bool.false, Bool.false, Bool.true, Bool.true, Bool.true, Bool.true]
        16 -> [Bool.false, Bool.true, Bool.false, Bool.false, Bool.false, Bool.false]
        17 -> [Bool.false, Bool.true, Bool.false, Bool.false, Bool.false, Bool.true]
        18 -> [Bool.false, Bool.true, Bool.false, Bool.false, Bool.true, Bool.false]
        19 -> [Bool.false, Bool.true, Bool.false, Bool.false, Bool.true, Bool.true]
        20 -> [Bool.false, Bool.true, Bool.false, Bool.true, Bool.false, Bool.false]
        21 -> [Bool.false, Bool.true, Bool.false, Bool.true, Bool.false, Bool.true]
        22 -> [Bool.false, Bool.true, Bool.false, Bool.true, Bool.true, Bool.false]
        23 -> [Bool.false, Bool.true, Bool.false, Bool.true, Bool.true, Bool.true]
        24 -> [Bool.false, Bool.true, Bool.true, Bool.false, Bool.false, Bool.false]
        25 -> [Bool.false, Bool.true, Bool.true, Bool.false, Bool.false, Bool.true]
        26 -> [Bool.false, Bool.true, Bool.true, Bool.false, Bool.true, Bool.false]
        27 -> [Bool.false, Bool.true, Bool.true, Bool.false, Bool.true, Bool.true]
        28 -> [Bool.false, Bool.true, Bool.true, Bool.true, Bool.false, Bool.false]
        29 -> [Bool.false, Bool.true, Bool.true, Bool.true, Bool.false, Bool.true]
        30 -> [Bool.false, Bool.true, Bool.true, Bool.true, Bool.true, Bool.false]
        31 -> [Bool.false, Bool.true, Bool.true, Bool.true, Bool.true, Bool.true]
        32 -> [Bool.true, Bool.false, Bool.false, Bool.false, Bool.false, Bool.false]
        33 -> [Bool.true, Bool.false, Bool.false, Bool.false, Bool.false, Bool.true]
        34 -> [Bool.true, Bool.false, Bool.false, Bool.false, Bool.true, Bool.false]
        35 -> [Bool.true, Bool.false, Bool.false, Bool.false, Bool.true, Bool.true]
        36 -> [Bool.true, Bool.false, Bool.false, Bool.true, Bool.false, Bool.false]
        37 -> [Bool.true, Bool.false, Bool.false, Bool.true, Bool.false, Bool.true]
        38 -> [Bool.true, Bool.false, Bool.false, Bool.true, Bool.true, Bool.false]
        39 -> [Bool.true, Bool.false, Bool.false, Bool.true, Bool.true, Bool.true]
        40 -> [Bool.true, Bool.false, Bool.true, Bool.false, Bool.false, Bool.false]
        41 -> [Bool.true, Bool.false, Bool.true, Bool.false, Bool.false, Bool.true]
        42 -> [Bool.true, Bool.false, Bool.true, Bool.false, Bool.true, Bool.false]
        43 -> [Bool.true, Bool.false, Bool.true, Bool.false, Bool.true, Bool.true]
        44 -> [Bool.true, Bool.false, Bool.true, Bool.true, Bool.false, Bool.false]
        45 -> [Bool.true, Bool.false, Bool.true, Bool.true, Bool.false, Bool.true]
        46 -> [Bool.true, Bool.false, Bool.true, Bool.true, Bool.true, Bool.false]
        47 -> [Bool.true, Bool.false, Bool.true, Bool.true, Bool.true, Bool.true]
        48 -> [Bool.true, Bool.true, Bool.false, Bool.false, Bool.false, Bool.false]
        49 -> [Bool.true, Bool.true, Bool.false, Bool.false, Bool.false, Bool.true]
        50 -> [Bool.true, Bool.true, Bool.false, Bool.false, Bool.true, Bool.false]
        51 -> [Bool.true, Bool.true, Bool.false, Bool.false, Bool.true, Bool.true]
        52 -> [Bool.true, Bool.true, Bool.false, Bool.true, Bool.false, Bool.false]
        53 -> [Bool.true, Bool.true, Bool.false, Bool.true, Bool.false, Bool.true]
        54 -> [Bool.true, Bool.true, Bool.false, Bool.true, Bool.true, Bool.false]
        55 -> [Bool.true, Bool.true, Bool.false, Bool.true, Bool.true, Bool.true]
        56 -> [Bool.true, Bool.true, Bool.true, Bool.false, Bool.false, Bool.false]
        57 -> [Bool.true, Bool.true, Bool.true, Bool.false, Bool.false, Bool.true]
        58 -> [Bool.true, Bool.true, Bool.true, Bool.false, Bool.true, Bool.false]
        59 -> [Bool.true, Bool.true, Bool.true, Bool.false, Bool.true, Bool.true]
        60 -> [Bool.true, Bool.true, Bool.true, Bool.true, Bool.false, Bool.false]
        61 -> [Bool.true, Bool.true, Bool.true, Bool.true, Bool.false, Bool.true]
        62 -> [Bool.true, Bool.true, Bool.true, Bool.true, Bool.true, Bool.false]
        63 -> [Bool.true, Bool.true, Bool.true, Bool.true, Bool.true, Bool.true]
        _ -> crash "This can never happen because I have listed every possible U8 value."

view this post on Zulip Brendan Hansknecht (Jun 13 2023 at 05:26):

U8 is 0 to 255

view this post on Zulip Brendan Hansknecht (Jun 13 2023 at 05:27):

I think if you listed all 256 values, it should be exhaustive and not need _ ->, but I have never tested

view this post on Zulip Hannes (Jun 13 2023 at 05:30):

Ah! Thank you! Had a lapse of memory there!

view this post on Zulip Notification Bot (Jun 13 2023 at 05:31):

Hannes has marked this topic as resolved.

view this post on Zulip Hannes (Jun 13 2023 at 05:34):

By the way, I just added the missing cases, so now the pattern match is on all integers from 0 to 255 and it still asks me to add the default case

view this post on Zulip Richard Feldman (Jun 13 2023 at 11:12):

Brendan Hansknecht said:

I think if you listed all 256 values, it should be exhaustive and not need _ ->, but I have never tested

I think in theory that's true, but in practice I don't think we actually check this at the moment :big_smile:

view this post on Zulip Notification Bot (Jun 13 2023 at 11:12):

Richard Feldman has marked this topic as unresolved.

view this post on Zulip Richard Feldman (Jun 13 2023 at 11:12):

I'm pretty sure for string and number literals we just treat them as "can never be exhaustive"

view this post on Zulip Brian Carroll (Jun 13 2023 at 12:14):

If you want this kind of behaviour you would have to create a tag union with 256 tags.

view this post on Zulip Brian Carroll (Jun 13 2023 at 12:14):

If I wanted to do that, I'd probably open up some REPL and get it to print out all of those numbers, then copy it into a .roc file and do some multi-cursor editing.

view this post on Zulip Fábio Beirão (Jun 13 2023 at 12:18):

It feels like this could be a slippery slope, specially with the concept of Nat which as far as I understand it is CPU-architecture dependent :thinking:

view this post on Zulip Fábio Beirão (Jun 13 2023 at 12:21):

If I understand correctly, if you find yourself doing when someNumber is 1 -> this; 2 -> that; 3 -> otherThing then you might be in need of domain-oriented tags which give meaningful domain names to what 1, 2 or 3 mean.
Anything that is working in the realm of having to exaustive pattern match U8 or I8 seems to be more of a formula/function waiting to be discovered

view this post on Zulip Folkert de Vries (Jun 13 2023 at 12:23):

hmm, so in rust they have 1..24 patterns, which make it much easier to match exhaustively on numeric types

view this post on Zulip Fábio Beirão (Jun 13 2023 at 12:24):

Do we have such a pattern in roc?

view this post on Zulip Fábio Beirão (Jun 13 2023 at 12:52):

(my statement above about a function waiting to be discovered did make me curious "okay, so how would I implement such function in roc?"
I came up with this, I wonder if there are more ideomatic ways to accomplish what I am doing here. I feel awkward about the {} |> List.repeat bitsToReturn but I'm not sure of a better way to accomplish a "while loop")

byteToBits : U8 -> Str
byteToBits = \byte ->
    bitsToReturn = 8

    {}
    |> List.repeat bitsToReturn
    |> List.walk
        (List.withCapacity bitsToReturn, byte)
        (\(bits, remaining), {} -> (bits |> List.append (remaining % 2), remaining // 2))
    |> (\(bits, _) -> bits |> List.reverse |> List.map Num.toStr |> Str.joinWith "")

## ── TESTS ────────────────────────────────────────────────────────────────────

## Tests for byteToBits

expect
    expected = [
        (0u8, "00000000"),
        (1u8, "00000001"),
        (10u8, "00001010"),
        (255u8, "11111111"),
    ]
    actually = expected |> List.map (\(byte, _) -> (byte, byte |> byteToBits))
    expected == actually

view this post on Zulip Richard Feldman (Jun 13 2023 at 12:57):

Fábio Beirão said:

Do we have such a pattern in roc?

we don't currently, but it seems like a reasonable thing to bring up in #ideas!


Last updated: Jul 06 2025 at 12:14 UTC