4 ms·
I might suspect that if you are lumping all statically-typed languages into a single bucket without making particular distinction among them, then you might not
by arwhatever 1y ago
I might suspect that if you are lumping all statically-typed languages into a single bucket without making particular distinction among them, then you might not have fully internalized the implications of union (aka Rust enum aka sum) typed data structures combined with exhaustive pattern matching.
I like to call it getting "union-pilled" and it's really hard to accept otherwise statically-typed languages once you become familiar.
- JoshTriplett 1y agoOr the fact that Rust's type system includes things like Send and Sync, which aren't tracked and enforced in many otherwise-statically-typed languages. C is statically typed, but its type system tracks much less.
- 1718627440 1y agoMy interpretation is, that C doesn't have data types, but memory layout types.
- gf000 1y agoArguably hardly even that, as memory layout for many in-built types are target and/or compiler-specific.
- ModernMech 1y agoenums + match expressions + tagged unions are the secret sauce of Rust.
- mixmastamyk 1y agoMaybe I need to read it again, but I remember the Rust book saying… you can use enums like C, but what if instead you used them in this more concise way? (Match on members, without the container.) Ok, was able to proceed but don’t feel like I understand what they really are.
- pjmlp 1y agoLike this code snippet? (* Expressions *) type Exp = UnMinus of Exp | Plus of Exp * Exp | Minus of Exp * Exp | Times of Exp * Exp | Divides of Exp * Exp | Power of Exp * Exp | Real of float | Var of string | FunCall of string * Exp | Fix of string * Exp ;; let rec tokenizer s = let (ch, chs) = split s in match ch with ' ' -> tokenizer chs | '(' -> LParTk:: (tokenizer chs) | ')' -> RParTk:: (tokenizer chs) | '+' -> PlusTk::(tokenizer chs) | '-' -> MinusTk::(tokenizer chs) | '*' -> TimesTk::(tokenizer chs) | '^' -> PowerTk::(tokenizer chs) | '/' -> DividesTk::(tokenizer chs) | '=' -> AssignTk::(tokenizer chs) | ch when (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') -> let (id_str, chs) = get_id_str s in (Keyword_or_Id id_str)::(tokenizer chs) | ch when (ch >= '0' && ch <= '9') -> let (fl_str, chs) = get_float_str s in (RealTk (float (fl_str)))::(tokenizer chs) | '$' -> if chs = "" then [] else raise (SyntaxError ("")) | _ -> raise (SyntaxError (SyntErr ())) ;; Hint, this isn't Rust.
- lmm 1y agoYes, the secret of Rust is that it offers both a) some important but slightly subtle language features from the late '70s that were sadly not present in Algol '52 and are therefore missing from popular lineages b) a couple of party tricks, in particular the ability to outperform C on silly microbenchmarks; b) is what leads people to adopt it and a) is what makes it non-awful to program in. Yes it's a damning indictment of programming culture than people did not adopt pre-Rust ML-family languages, but it could be worse, they could be not adopting Rust either.
- hollerith 1y ago>important but slightly subtle language features from the late '70s Programming-language researchers didn't start investigating linear (or affine) types till 1989. Without the constraint that vectors, boxes, strings, etc, are linear, Rust cannot deliver its memory-safety guarantees (unless Rust were radically changed to rely on a garbage collecting runtime). >it's a damning indictment of programming culture than people did not adopt pre-Rust ML-family languages In pre-Rust ML-family languages, it is harder to reason about CPU usage, memory usage and memory locality than it is in languages like C and Rust. One reason for that is the need in pre-Rust ML-family langs for a garbage collector. In summary, there are good reasons ML, Haskell, etc, never got as popular as Rust.
- b_e_n_t_o_n 1y agoAfaik they aren't true unions but sum types, which have different implications. And fwiw I've used unions in typescript extensively and I'm not convinced that they're a good idea. They give you a certain flexibility to writing code, yes, does that flexibility lead to good design choices, idk.
- kibwen 1y agoTypeScript unions are very different from Rust enums, and they lead to different design decisions. IMO Rust-style tagged unions are essential in any new language coming out today, it's really a pity that people didn't pick up on this in the 70s.
- b_e_n_t_o_n 1y agoWhat differences do they lead to? I just find union types more flexible, with the perceived downside of losing some semantics on success/error results (but I don't think it's a problem personally). But I wouldn't say I'm super knowledgeable about Rust outside of writing some toy programs. You could create your own Result<T, Error> type in TS but people don't really do that outside of ecosystems like Effect because there isn't usually a reason to.
- uzerfcwn 1y agoThe core difference between sums and unions is that sum types have cardinality |A+B| = |A| + |B|, whereas union types have cardinality |A∪B| = |A| + |B| - |A∩B|. As you noted, type systems with union types, such as Typescript, also support sum types because you can create disjoint wrapper types (like Ok and Err) and their union type will be semantically equivalent to a sum type, since the intersection is an empty set. In this sense, union types are strictly more powerful than sum types. The downside is that union types require some notion of subtyping, since otherwise A∩B is always empty for distinct A and B. Unfortunately, subtyping is apparently very difficult to implement in HM-like type systems (like Rust and ML) such that it plays well with type inference.[0] Hence, the downside of having union types in a language is that users have to write out types more often. Unlike kibwen, I don't think Rust's type system is particularly essential for new languages. It's a design choice where one side has more powerful types but the other side has more powerful type inference. [0] https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system#Subtyping https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_sy...
- arwhatever 1y agoI'm way late to think of this and circle back here, but if you don't mind briefly switching languages to F#, I first grasped these concepts via "Designing with types: Making illegal states unrepresentable" https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/ https://fsharpforfunandprofit.com/posts/designing-with-types... ... and I would suggest actively playing around with the code examples, extending them, breaking them intentionally etc. somewhere like https://dotnetfiddle.net/ https://dotnetfiddle.net/ I think the article is brilliant but as I recall I wasn't able to grasp the concepts from reading alone. That same website has a separate article on building a calculator (in code) that I recall was also excellent.