5 ms·
> But a value can have more than one possible type. The number 1 could come from an unsigned byte, or a signed long, for example. Here we disagree. Unsigned 8-
by Twey 7d ago
> But a value can have more than one possible type. The number 1 could come from an unsigned byte, or a signed long, for example.
Here we disagree. Unsigned 8-bit integers† and signed 64-bit integers, while both conveniently notated with Arabic numerals, are actually different values that support different operations, for example negation. They share quite a few similarities in how their operations interact with one another, for example each (assuming wrapping) is a monoid with 0 and +, but the semantics of the actual operations differs if looked at more closely. Mathematics agrees: the element ‘1’ of N/2⁸ and the element ‘1’ of Z/2⁶⁴ are not the same thing (their encodings coincide sometimes, but it's poor form to make assumptions about it).
† Bytes are data but not numbers, and so support only data operations like duplication and discarding, not number operations like adding and multiplication: as an artefact of representation you can usually ask the hardware or programming language to manipulate them as if they were numbers, but the result remains meaningless.
> The point is that you may not have anticipated that not everyone would know their date of birth, and the data you receive is invalid according to your earlier assumptions.
But that's exactly what I'm saying: you must have made some assumptions about that missing data, otherwise there is no safe thing you can do to it (including discarding it, which is a popular choice). This works in Clojure only because Clojure couples some semantics (data semantics plus operations on runtime type information) into every value, i.e. it restricts what values are even representable in the language in order to ensure that this function will always be safe to write.
In more strictly typed languages you can still talk about values that support these behaviours, but you are required to be explicit about it, because there are some values that can be represented that don't support these operations.
> Functions that don't require the date of birth will continue to work with no change required.
Any function that takes the date of birth ’requires’ the date of birth (or more generally a possibly-empty set of ‘leftover’ values). The only thing that differs is what's required from it: some functions might require that it be a date while other functions only require that it be data, for example. The universally imposed limitation that all values must be coupled to data semantics and runtime type information is convenient for ergonomics if you write a lot of these functions (since you don't have to remember to write that assumption down), but it's important to remember that it is a coupling — the resulting values are more complex than values without those things bundled on, and the trade-off is that you can no longer talk about values for which they don't hold.
- weavejester 7d ago> Here we disagree. Unsigned 8-bit integers† and signed 64-bit integers, while both conveniently notated with Arabic numerals, are actually different values that support different operations, for example negation. What about a 32 bit unsigned integer and a 64 bit unsigned integer? Are they still separate values? You appear to be saying that the values of a type cannot be a subset of another type; that is, there is no '1', only a '1' that is an integer, a '1' that is a short, and so forth, and every '1' is distinct. Fine, that's a possible way of looking at it, but why is that more valid or consistent than a model that allows subtypes? That, for example, the value '1' could be both a Number, an Integer, and a NaturalNumber? Further, what's the practical difference between a type defined as consisting of the numbers [1 2 3], and an integer that's restricted to those values? > This works in Clojure only because Clojure couples some semantics (data semantics plus operations on runtime type information) into every value, i.e. it restricts what values are even representable in the language in order to ensure that this function will always be safe to write. Even if we view a Clojure value as a coupling between type and data, that's only a coupling between two things, and it ensures we can avoid further coupling caused by large record types. On net, we reduce the amount of coupling a statically typed language that uses closed record types would require. For instance: (defn average-wbc-count [{:patient/keys [wbc-counts]}] (/ (apply + wbc-counts) (count wbc-counts))) This function is coupled to only one key/value pair. Any other information in the map is irrelevant, which is why an invalid :patient/birthdate doesn't cause the function to fail. There's no coupling between :patient/birthdate and average-wbc-count. Conversely: fn average_wbc_count(patient: &Patient) -> f64 { patient.wbc_counts.iter().sum::<f64>() / patient.wbc_counts.len() as f64 } This function requires patient to be a Patient struct, and therefore the function is implicitly coupled to every field in the struct, regardless of whether that field is ever actually used. I need to ensure that a patient has some birthdate that's an anticipated type (even if that's an error type) before I can call the function.
- Twey 7d ago> You appear to be saying that the values of a type cannot be a subset of another type; that is, there is no '1', only a '1' that is an integer, a '1' that is a short, and so forth, and every '1' is distinct. That's true, and you can prove it simply by observing that they react differently to ‘the same’ operations, e.g. (2¹⁶ - 1) + 1 has a different value (that responds differently to tests like `≥ 0`) depending on which type we're talking about. There's an injection into the larger type (a type coercion) that is very well-behaved, but it's not an identity map — it changes the operations on the value. > Further, what's the practical difference between a type defined as consisting of the numbers [1 2 3], and an integer that's restricted to those values? Nothing (ish: you have to be careful to describe what happens to all the integer operations when restricted to your type) — that's a totally valid way to define a type. But it's not the only way to define a type, because the world of types is much bigger than the world of restricted sets of integers. > I need to ensure that a patient has some birthdate that's an anticipated type No, you just have to be explicit about the possibility of a birthdate (or other fields) being of a wide type, e.g. struct Patient { wbc_count: Vec<u64>, birth_date: Box<dyn Any>, other_fields: HashMap<String, Box<dyn Any>>, } Clojure just attaches that by default to every value.