3 ms·
Misery is trying to retrofit "bool", True/False, and nil/null to a language. C had to do that. Python had to do that. Getting those wrong is one of the classic
by Animats 1mo ago
Misery is trying to retrofit "bool", True/False, and nil/null to a language. C had to do that. Python had to do that. Getting those wrong is one of the classic language design mistakes. It seems like treating "True" as a value that equates to 1 will work, but then the special cases get you. Like being able to perform arithmetic on True.
Common language design boners:
- Not building in strings. That's now in the past. Everybody has strings. (Well, C...)
- Not building in multidimensional arrays of the numeric types. Everything that number-crunches needs them, and having multiple definitions is Not Fun and may lead to expensive re-copying between different libraries.
This is an enormous blind spot in language design. It's one of the reasons FORTRAN, which has good multidimensional numeric arrays, is still often used for number-crunching.
- Not standardizing the small vectors (vec2, vec3, vec4) and their matrix friends. Graphics code depends on these, and it's really annoying if there are multiple slightly incompatible implementations. Especially since GPUs have hardware for those types, and you want CPU and GPU to use the same representations.
- Not having arrays of bits. Pascal had PACKED ARRAY[0..N] of BOOLEAN but that was lost in later languages. It's useful to have that as a language construct, because most modern CPUs have good hardware for dealing with bit strings, and you'd like the compiler to use it.
Most useful languages acquire these features, but, when they come in late, there are multiple similar implementations, and libraries made incompatible by depending on different implementations.
(Amusingly, when Second Life switched from Linden Scripting Language to Luau, they initially had True, TRUE, and true all in use, as different types with different semantics. I was able to persuade the devs to unify the boolean types.)
- dwattttt 1mo ago> Not having arrays of bits. Pascal had PACKED ARRAY[0..N] of BOOLEAN but that was lost in later languages. It's useful to have that as a language construct, because most modern CPUs have good hardware for dealing with bit strings, and you'd like the compiler to use it. I'm not sure exactly which features are responsible (I'm inclined to blame templates), but C++'s std::vector<bool> is a rough edge. For those unfamiliar, the standard specifies this vector template in a way that's not compatible with other vectors.
- Lvl999Noob 1mo agoAgreed. Instead of special casing Boolean arrays to be packed, it's better to have standard Boolean arrays and bitarrays as separate types.
- NekkoDroid 1mo agoThey should have made `std::bitset<std::dynamic_extent>` what todays `std::vector<bool>` is (actually maybe not, `std::bitset` is fixed sized, just compile time fixed size). While at it also make `std::array<std::dynamic_extent>` a runtime fixed size array.
- DarkUranium 1mo agoVectors & multidimesional arrays are something I'm 100% adding to my language's core. It kind of started with vectors as the very first feature (I was sick & tired of libraries reinventing their own `Point`/`VectorN` in incompatible ways).
- AdamH12113 1mo agoStrings are a really weird data type. I'm not sure you can do much better than C strings without implicitly requiring dynamic memory allocation, which C deliberately does not do. Definitely agree on multidimensional arrays. I feel like efficient arrays in general are underrated in high-level language design.
- tialaramex 1mo ago> Strings are a really weird data type. I'm not sure you can do much better than C strings without implicitly requiring dynamic memory allocation, which C deliberately does not do. The thing you want is what Rust delivers in the box, &str a string slice reference type, in Rust's case the "string" is UTF-8 encoded text. On the bare metal the way to represent this type is as a "fat pointer" typically a pair of registers, one with the address of the first byte of the string and the other with a length. C should have fat pointers, they were proposed, for IIRC C89 but the proposal was rejected. That's pretty sad, the fat pointer is expensive to the point of maybe feeling extravagant on a PDP-11, but by 1989 that's long gone. More ridiculously C++ didn't get this type (which it eventually called std::string_view and provides in its standard library not as a built-in) until 2017, years after Rust 1.0 shipped. In the meanwhile C++ just did not have a sensible way to do this, strings are hard apparently. The string buffer feature, allowing you to actually make strings is less important, as you say it will need an allocator and so on very bare metal you might not have this - but the string slice reference doesn't need an allocator. I think it's worth delivering the basic "it's a growable array type, duh" implemenation of the string buffer type, which is what Rust's String type is, but C++ chooses to ship an oddly specific small-string optimized version as std::string right from the offset.
- taylor-tg 1mo agoOh wow, I had no idea that SL did another language change after migrating LSL to Mono. Surprising considering that happened late '00s/early '10s? I'd consider LSL to have been foundational in my ultimate interest/career in software engineering. The strict typing, very usable compile/runtime errors, and good documentation/examples made it so easy to pick up as a teen. Not to mention as long as you didn't edit/save a script again it would always run the same regardless of updates.
- tialaramex 1mo agoYou list a few absences but absences aren't the end of the world, I say it's worse when designers make a booboo where the language semantics are wrong. In C++ there are so many of these it's not sporting but a recurring example from the garbage collected languages would be the for-each loop mistake. Several times now†, people make a language where the way a for-each loop (for each Goose in Geese ...) works is that there's a single variable Goose and each time around the loop we change which value is referred to by the Goose variable. This seems intuitively like a reasonable way to do this. But it's wrong and eventually your programmers will get nasty surprises. What you actually should deliver is an implementation where each time around the loop there's a new variable named Goose, that variable goes away at the end of that iteration and will be replaced by the next one, with the same exact name. † At least Go and C#, I think there are others
- lelanthran 1mo ago> What you actually should deliver is an implementation where each time around the loop there's a new variable named Goose, that variable goes away at the end of that iteration and will be replaced by the next one, with the same exact name. Is this because a closure inside a loop will capture a reference to `Goose`? I think that this is a capture problem not a variable problem. The closure should always do the right thing and capture the value of all variables (not just ones inside the loop), instead of capturing the reference to the variables. Then the general problem is fixed to match what developers expect, instead of a specific instance of that class of problems being fixed and working differently to how other captured variables work.
- tialaramex 1mo ago> I think that this is a capture problem not a variable problem. The closure should always do the right thing and capture the value of all variables (not just ones inside the loop), instead of capturing the reference to the variables. Now your "lalanthran closures" can't mutate the world because they work exclusively with copies not references, if they try to mutate something then whatever they're touching was just a copy not the real thing.