In my code, I have plenty of dynamic maps that I have typed in the following style:
%{optional(String.t()) => integer()}
The idea here being that there are string–integer pairs, but it could also be empty. But is this actually correct? What trips me up is the wording in the TypeSpec documentation:
| %{} # empty map
| %{key_type => value_type} # map with required pairs of key_type and value_type
| %{required(key_type) => value_type} # map with required pairs of key_type and value_type
| %{optional(key_type) => value_type} # map with optional pairs of key_type and value_type
What do “required pairs” and “optional pairs” actually mean? As I see it, there are a couple of interpretations:
- Required means that no other key–value types are allowed, but there can be 0…n key–value pairs. Optional means that there can also be other key–value types.
- Required means that there is at least one key–value pair matching this spec, optional means 0…n. Both required and optional restrict the key–value types to only match the mentioned.
If we think about option 1, then what is the difference between %{required(a) => b, required(c) => d}
and %{required(a) => b, optional(c) => d}
? Are they the same? Also, is %{optional(a) => b}
completely worthless?
Or if option 2 is true, then I don’t find required
very useful at all, because usually I can’t guarantee that dynamic data (for example from the user) has at least one key–value pair. But then how can I type a map that at least has string–integer pairs, but might have others too?
But whichever the case is (or something other than what is listed), I can’t deduce it from the docs, so I’d like to improve them on this part. I just don’t know what is the correct interpretation.