3 ms·
> Perhaps we only want to find patients with WBC counts outside a certain range, and discard those lines where the data is invalid. In which case, we could writ
by Twey 24d ago
> Perhaps we only want to find patients with WBC counts outside a certain range, and discard those lines where the data is invalid. In which case, we could write a narrower type, and simply not parse the CSV rows that are invalid:
These two types aren't typing the same data, though: in the latter case (in Rust) you've actually thrown away some of the data, and the type reflects that. In some languages like Clojure or TypeScript you can't distinguish that from the case where you haven't actually thrown away the data and are secretly carrying it around as well as whatever data is explicitly typed there, but if you want to get it back you have to know that it was once there, i.e. have additional (conceptual) type information that you chose not to write down.
> Granted, but Clojure's approach can result in greater decoupling with less effort, particularly when dealing with imperfect data.
I think this isn't a great comparison: the usual vehicle for this thing in Rust is the trait, which manages to abstract over the representation statically without introducing a bunch of runtime machinery for it (though you can opt in to the machinery by using a trait object!). That's very idiomatic in Rust, although the trait syntax is a bit noisier (because it's more general).
> the entire problem of how we represent this data in memory is sidestepped.
It's not sidestepped — the language just makes an opinionated choice for you, which you can't avoid.
> these schema definitions are independent of the type system (i.e. decoupled), so we can apply them selectively or not at all
These are just predicates; I don't think they have much to do with this discussion? They don't help you to type your data: you (the programmer) still have to carry that type information around in your head in order to work with the values even after they are validated.
> Could you do the same in Rust or other similar languages? Sure, it's ultimately just maps and predicates. But many languages aren't geared up to make that pleasant to use.
There are good reasons for that, though. Nominal typing allows you to express and enforce constraints that aren't necessarily enforced by the structure of the type, at the cost of some extra ceremony. And systems languages often want to be able to express types that cannot be treated this way: types in which there is no sensible way to ‘ignore’ a value. Clojure makes some things easier to write by forcing you to carry around a bunch of extra stuff (both runtime data and static semantics) with all your values; that's a perfectly valid choice that makes an important subset of programs nicer to write, but it also excludes values that don't fit into that category or can't reasonably be coupled to their RTTI.
As we get better at writing compilers we're increasingly seeing a move towards mechanisms like Rust's traits or C++'s concepts that allow the programmer to actually abstract over the representation of a type (as opposed to forcing a uniform representation as is common in dynamically typed languages). If you do that well you can get the best of both worlds semantically, but these kinds of systems languages will always be a bit heavier syntactically because the syntax needs to support a wider range of types.
- weavejester 24d ago> These two types aren't typing the same data, though: in the latter case (in Rust) you've actually thrown away some of the data, and the type reflects that. In Clojure the data is thrown away too, just at a slightly later point. The data is parsed then destructured then processed, and it is at the destructuring stage that irrelevant data is discarded and marked for GC. Just as the Rust function avoids coupling by using a more narrow type, the Clojure function avoids coupling by using a more narrow binding. > I think this isn't a great comparison: the usual vehicle for this thing in Rust is the trait, which manages to abstract over the representation statically without introducing a bunch of runtime machinery for it But you have to create and implement those traits. I'm not saying this is impossible in Rust; just a lot more onerous because you don't get all the machinery Clojure has that makes it trivial. > These are just predicates; I don't think they have much to do with this discussion? This might be why we're talking past each other. When I say "type", I mean a set of possible values. I don't think this is an uncommon definition; the first sentence of the "data type" Wikipedia page pretty much defines it the same way. Given this definition, you perhaps see why we can narrow a runtime type by composing it with a predicate. My understanding is that you view types differently, as more of an interpretation of some sequence of bits, rather than a set of data values. Is that correct? > As we get better at writing compilers we're increasingly seeing a move towards mechanisms like Rust's traits or C++'s concepts that allow the programmer to actually abstract over the representation of a type (as opposed to forcing a uniform representation as is common in dynamically typed languages). I agree that's there's no reason in principle that you couldn't statically type it with a sufficiently advanced compiler.
- Twey 23d ago> In Clojure the data is thrown away too, just at a slightly later point. The data is parsed then destructured then processed, and it is at the destructuring stage that irrelevant data is discarded and marked for GC. Right; the difference is that by the destructing time you have lost the type information about that ‘discarded’ data, meaning you no longer know how to discard it. In Clojure this is not a problem, because the only values you're allowed to have are those that can be garbage collected, and (furthermore) garbage collected by calling a known function with nothing more than a universal set of GC metadata that is attached at a standard location on every value. But this is only possible because of a large restriction Clojure places on allowable values: the base ‘unitype’ of all Clojure values already contains all of this extra data, and any values for which it can't be attached or it doesn't make sense are simply forbidden. > But you have to create and implement those traits. I'm not saying this is impossible in Rust; just a lot more onerous because you don't get all the machinery Clojure has that makes it trivial. On this I absolutely agree. The only thing I'd be careful to distinguish is not simply that Rust doesn't have the machinery but that the machinery makes sense only given a fairly onerous set of additional assumptions about your values that Rust doesn't make (and that are usually held to be incompatible with a systems programming language, but IMO are also helpful for many — though not all — ‘userland’ programs). > When I say "type", I mean a set of possible values. I don't think this is an uncommon definition; the first sentence of the "data type" Wikipedia page pretty much defines it the same way. It's not an uncommon one, indeed, and while there are more sophisticated definitions (e.g. including equality) I don't think they need to be invoked for this discussion. > Given this definition, you perhaps see why we can narrow a runtime type by composing it with a predicate. No, you're right: I think I was thrown off by your assertion that these are uncoupled from the type system when we were talking originally of ‘conceptual’ types. > My understanding is that you view types differently, as more of an interpretation of some sequence of bits, rather than a set of data values. Is that correct? Quite the opposite. These words aren't entirely standardized so I set out my definitions above: - I am using ‘value’ to refer to any object of discussion in a programming language that nominally exists at runtime; effectively, a value can be defined by the set of operations available on it - I am using ‘data’ to refer to values that have ‘data-like’ semantics, i.e. has ‘default’ move and discard mechanism like bitwise copy/overwrite or GC