3 ms·
> Don't all types need names, regardless of what language you use? No. - In very dynamic languages (like javascript), most types arguably don't have names at
by josephg 2y ago
> Don't all types need names, regardless of what language you use?
No.
- In very dynamic languages (like javascript), most types arguably don't have names at all. For example, I can make a function to add 2d vectors together. Even though I can use 2d vectors in my program, there doesn't have to be a 2d vector type. (Eg, const vecAdd = (a, b) => ({x: a.x+b.x, y: a.y+b.y}) ).
- Most modern languages have tuples. And tuples are usually anonymous. For example, in rust I could pass around 2d vectors by simply using tuples of (f64, f64). I can even give my implicit vector type functions via the trait system.
- In typescript you can have whole struct definitions be anonymous if you want to. Eg: const MyComponent(props: {x: number, y: string, ...}) {...}.
- There's also lots of types in languages like typescript and rust which are unfortunately impossible to name. For example, if I have this code:
#[derive(Eq, PartialEq)]
enum Color { Red, Green, Blue }
fn foo(c: Color) {
if c == Color::Red { return; }
// What is the type of 'c' here?
}
Arguably, c is a Color object. But actually, c must be either Color::Green or Color::Blue. The compiler understands this and uses it in lots of little ways. But unfortunately we can't actually name the restricted type in the program.
Rust can do the same thing with integers - even though (weirdly) it has no way to name an integer in a restricted range. For example, in this code the compiler knows that y must be less than 256 - so the if statement is always false, and it skips the if statement entirely:
https://rust.godbolt.org/z/3nTrabnYz https://rust.godbolt.org/z/3nTrabnYz
But - its impossible to write a function that takes as input an integer that must be within some arbitrary range.
- relaxing 2y agoThe named integer range thing is interesting. I guess it depends on what you goal is. Could you use asserts? Could you wrap the integer in an object and embed the restriction logic there?
- whilenot-dev 2y agoDoable in TypeScript[0], but I'd wager if that's really necessary. [0]: https://www.typescriptlang.org/play/?#code/C4TwDgpgBAogdgVwLYQE4ENgQDwDkoQAeWcAJgM5SJIBGaANFAIIDGLBxEZl1dqA2gF0oAXihCAfKOZt+AcgA2XAObAAFnOFESFKLgBQUKAH4ZLfrzSDDUAFyxqaTDlyN+AOk+sWjb-KVwqhqCghIA3Pr6oJBQAJJwwABK6IE4AGIcOjzIfIwAKplcupaoUmIwhCwKCKQ48MhOWNh5Eoz1KBhNaRLhkdHQBWLxSSnKOABMAAyMAIyTk+FAA https://www.typescriptlang.org/play/?#code/C4TwDgpgBAogdgVwL... from StackOverflow[1] [1]: https://stackoverflow.com/questions/39494689/is-it-possible-to-restrict-number-to-a-certain-range https://stackoverflow.com/questions/39494689/is-it-possible-...
- josephg 2y ago> but I'd wager if that's really necessary. I think its more useful in a language like rust, because the compiler can use that information to better optimize the emitted assembly.
- whilenot-dev 2y agoEven in compiled languages I think the benefits won't outweigh the difficulties in compiler optimization. It starts with the question of how I would like to design my sequences: - Is it the range (0, 10] in steps of 1? - Is it the range (0, 2^10) in steps by the power of 2? - Is it the range (0.0, 2PI] in steps of EPSILON? How would the compiler engineer generalize this optimization? And the question would continue whether I'd be really able to define them that precisely before runtime. Most applications are just dealing with lightweight structures, mostly serialized JSON nowadays, and then even there are enough fuck-ups[0] where such an optimization wouldn't help at all. I can imagine the places where they really matter are some heavy memory intensive data structures like deep neural networks, graphics or video and the like - for the time being they're just dealing with tensors of data type floatX, and that seems to be fine AFAIK. I mean, I'd be really nice if the smaller memory footprint could come out-of-the-box during compilation. But all the CLI tools written in rust certainly don't have the use case to put this complication on the shoulders of compiler research. [0]: https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times-by-70/ https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times...
- josephg 2y agoYou could do it with asserts or as a wrapped type, but both of those approaches are only checked at runtime. That means you don’t get runtime errors instead of compiler errors. It also limits the compiler’s ability to take advantage of the constraint to optimise. Also asserts need to be added everywhere - by both you and the compiler. And wrapped types are annoying to use since you lose access to all the standard arithmetic operators. It would be better if the type system could encode types like this directly. Better for ergonomics and better for optimisation.
- whilenot-dev 2y ago> Arguably, c is a Color object. But actually, c must be either Color::Green or Color::Blue. The compiler understands this and uses it in lots of little ways. But unfortunately we can't actually name the restricted type in the program. I think that's less a question whether you can, but rather whether you should or shouldn't design it that way... (I'll use TypeScript here for the simpler syntax) It'd be perfectly fine to do something like this: type ColorR = 'red'; type ColorG = 'green'; type ColorB = 'blue'; type ColorRGB = ColorR | ColorG | ColorB; But which constraint should your new type ColorGB (your variable c) adhere to? // constraint A type ColorGB = ColorG | ColorB; // constraint B type ColorGB = Exclude<ColorRGB, ColorR>; I'd argue if the type ColorGB is only needed in the derived form from ColorRGB within a single scope, then just let the compiler do its control flow analysis, yes - it'll infer the type as constraint B. But if you really need to reuse the type ColorGB (probably some categorization other than all the colors), then you'd need to pay close attention to your designed constraint.
- josephg 2y agoI really wish I could do in rust what you're doing here in typescript. Say I have some enum like ColorRGB here. In some contexts, only a limited subset of variants are valid - say, ColorGB. There's a few ways to code this - but they're all - in different ways - horrible: 1. Use ColorRGB in all contexts. Use asserts or something to verify that the value is one of the expected variants. This fails at expressing what I want in the type system - and the code is longer, slower and more error prone as a result. 2. Have two enums, ColorRGB and ColorGB. ColorRGB is defined as enum ColorRGB { Red, Restricted(ColorGB) }. This lets me encode the constraint - since I can use ColorGB explicitly. But it makes Color harder to use - since I need to match out the inner value all over the place. 3. Have two enums, ColorRGB and ColorGB which both have variants for Green and Blue. Implement conversion methods (impl From) between the two types. Now I have two types instead of one. I have conversions between them. And I'll probably end up with duplicate methods & trait impls for ColorRGB and ColorGB. Luckily this doesn't come up that often. But - as your typescript example shows - it can just be expressed directly in the type system. And LLVM already tracks which variants are possible throughout a function for optimisations' sake. I wish rust had a way to express something like Exclude<ColorRGB, ColorR>.
- kirkhawley 2y ago"Most modern languages have tuples. And tuples are usually anonymous." Sure! And I LOVE ending up with var names that are always Item1 and Item2... Very descriptive!
- josephg 2y agoNobody’s saying anonymous tuples are necessarily a good idea. Just that lots of languages provide them.