Confused about the cardinality of a Result type

Reading this guide: Types as Sets · An Introduction to Elm

I have the same confusion mentioned by the OP here: Types and Sets - Cardinality of Custom type - #2 by Punie

Like the OP, I was thinking about the Bool value being part of a returned tuple (so I was imagining a cardinality of 6 if not 7):

| Res |  Bool | Color | Equiv             |
|  Ok |     T |     R | Ok (True, Red)    |
|  Ok |     T |     G | Ok (True, Green)  |
|  Ok |     T |     B | Ok (True, Blue)   |
|  Ok |     F |     R | Ok (False, Red)   |
|  Ok |     F |     G | Ok (False, Green) |
|  Ok |     F |     B | Ok (False, Blue)  |
| Err |     _ |     _ | Err String        |
|     | Total |     7 |

However, if Bool represents the Ok/Err state, I still fail to see how I should consider a cardinality of 5. In my mind, I see those branches.

| Res |  Bool | Color | Equiv      |
|  Ok |     T |     R | Ok Red     |
|  Ok |     T |     G | Ok Green   |
|  Ok |     T |     B | Ok Blue    |
| Err |     F |     _ | Err String |
|     | Total |     4 |

I suppose this means that mean that:

Ok(Red | Green | Blue)

is to be considered as part of an element of the “set” of possible values?

I think I’m a little confused since I’m thinking in terms of truth tables, and that’s how I tend to think about multiplying the complexity of code branches and I relate this to if/else statements.

Could anyone clarify this for me? I’d like to understand how considering cardinality could help me write better code.

I understand how ORing a type means you have to add the elements of the set to obtain the cardinality, but I fail to visualise what’s going on here.

The Bool in Result Bool Color isnt part of a tuple, and it doesnt represent the Ok/Err state.

I guess you have to know a bit about the Result error value type in Elm, which comes in 2 varietities:

Err error
Ok value

where the lowercase error and value names are stand-ins for some other type.

For Result Bool Color, error is Bool and Color is value, meaning the 5 possibilities are:

Err True
Err False
Ok Red
Ok Blue
Ok Green

Somewhat related sidetracking question: How do we count the cardinality of a type that includes a potentially infinite type like String as a parameter?

Result String Bool
Ok True
Ok False
Err "Anything goes…"

Is it 3 or something like 2+∞?

What about Result String Int? How is its cardinality expressed differently?

Thanks @Chadtech. Indeed I was confused about the Result type, thanks it makes perfect sense now.

@edgerunner, that’s how I understand it: 2 + ∞. Though in that case the infinity aspect isn’t much of a problem since we wouldn’t be doing anything complicated with the infinity of strings. I’d still be interested to hear when and if we still would want to reduce cardinality, maybe use an error dictionnary as a best practice?

Yes, the cardinality would be infinite for Result String Bool.

That said, depending on the use-case, you’re better off discarding the cardinality of String.

For instance, if you take the following example

type User
  = Anonymous
  | User String -- name of the user
  | Admin String -- name of the admin

If you compute the cardinality, it is again infinite, because you can have an infinite number of different User with different names, and same for Admin. That said, in your data modeling, you will probably not care about the cardinality of name, so you can ignore it. Then you can count again and assess that the cardinality of possible users is 3, which is likely more interesting to your use-case.

1 Like

What I understand from your answer is that the cardinality of a potentially infinite type depends on how I intend to branch on it. So maybe aList has a cardinality of 2, because it can be empty or non-empty, which makes a difference for me, or 3 if I also envision a different meaning for a single-element list. Does that make sense?

Related post on how the math works:

Shows how a data models type can be manipulated whilst keeping the cardinality the same - effectively different ways of representing the same intention.

Yes. “The cardinality of something depends on how you will branch on it” is a good way to put it. But being explicit about how you are going to branch on it is probably a good move. If you want to have different behaviors for different sizes of your list you might want to do

type MyList a
  = Empty
  | OneItem a
  | TwoItems a a
  | AtLeastThreeItems a a a (List a)

Here the cardinality of the type is infinite, but in a way, it’s also obvious that you’re going to have 4 different behaviours, thus in a way a cardinality of 4.

Let your data model guide how you will branch off explicitly instead of letting your case expressions determine that implicitly.

1 Like

My guess is that the confusion resides in reading Result Bool Color in two different ways.

The first one, is the obvious one for most Elm users which is “The type Result with the error parameter set to Bool and the success parameter set to Color”.
and since Result is defined as type Result error success = Ok success | Err error this automatically means that the cardinality of the type is cardinality of Bool + cardinality of Color (5 in the example).

The second way to read that is to read it as the definition of a tag. type MyResult = Result Bool Color. In this case we have the tag Result with two parameters: Bool and Color. In this case, the cardinality of MyResult is cardinality of Bool times cardinality of Color (6). The cardinality of MyResult is the same as the cardinality of a tuple (Bool, Color)

Once one understands that the custom type in Elm are Tagged Unions, also called “sum types” they understand the cardinality of that custom type. The cardinality of a sum type will always be the sum of the cardinalities of its branches.

It is about pattern match exhaustion. If the type has non-infinite cardinality, you can explicitly match each case and have the compiler complain when you haven’t treated a case.

Pattern matching also allows you to match against a variable. This effectively reduces the cardinality of the match for that specific variable to 1. You can also reduce the cardinality to a larger number by matching to explicit values before you match for the variable.

Thanks @pdamoc.

The confusion came from the fact that I took a week off elm learning, and came back to it with my mind slightly blurry about what the Result type actually represented (I had a looked at it before).

And I guess I couldn’t quite envision a boolean state returned in complement to an error state, and how it would be useful.

Rather, I wrongly thought bool was representing the error state which is how I would tend to think naturally (I’ve got an error, or I haven’t got an error)

Thanks for your feedback though, I like the idea of match exhaustion. It looks like a very interesting use of types and I think I’m slowly grokking it :wink:

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.