Statically typed languages give you u8/i8 types of numbers.
Maybe having a non empty string, or non empty list, type is useful now and then, but in practice, just have your code work just as well on both empty and non empty values, and you're good to go.
I'm pretty happy with the languages we have today overall (Kotlin and Rust at the top, but C#, Swift, and Java get an honorable mention).
Your comment kind of galvanizes my view that most of us suffer from Stockholm Syndrome with respect to our programming languages.
As another commenter said, some statically typed languages give you unsigned numbers. Maybe most of them do. But definitely not some of the most popular ones. And out of the ones that do, they are often really unhelpful.
C's unsigned numbers and implicit conversions are full of foot-guns.
Java basically doesn't have unsigned ints. It does kind of have this weird unsigned arithmetic API over signed ints, but it's really awkward and still bug-prone to use.
Kotlin's unsigned numbers API is very poor. Very. Kotlin does not give a crap if I write: `Int.MIN_VALUE.toUInt()`, so it's perfectly happy to just pretend a negative number is an unsigned number. Not to mention that unsigned number types are implemented as inline/value classes which don't even actually work correctly in the current version of the language (just go look at the bug tracker- I literally can't use value classes in my project because I've encountered multiple DIFFERENT runtime crash bugs since 1.5 was released). It, like Java and C, etc, is perfectly happy to wrap around on arithmetic overflow, which means that if you didn't guess your types correctly, you're going to end up with invalid data in your database or whatever.
Rust and Swift have good unsigned number APIs.
Notice also that I didn't say anything about non-empty collections. Yes, I absolutely want non-empty collection types, but that is no where NEAR as important and useful as non-empty strings, even though they might seem conceptually similar. I'm willing to assert, like the bold internet-man I am, that you and all of the rest of us almost NEVER actually want an empty string for anything. I never want to store a user with an empty name, I never want to write a file with an empty file name, I never want to try to connect to a URL made from an empty host name, etc, etc, etc. It is very often perfectly acceptable to have empty collections, though. It's also very frequent that you DO want a non-empty collection, which is why we should have both.
We don't even need empty strings (or collections, really) if we have null.
You say you like Kotlin and Rust and I work with both of them extensively. I can point out a great many shortcomings of Kotlin in particular. I used to be enamored with it, but the more I use it, the more shortcomings, edge cases, bugs, and limitations I run into. Rust is pretty great, but even that has some real issues- especially around how leaky the trait abstraction is. But at least Rust's excuse is that it's a low-level-ish systems language. It's these "app languages" that irritate me the most.
> We don't even need empty strings (or collections, really) if we have null.
This feels exactly backwards to me: I almost always want my sequence-like types to have a well-defined zero-length element, and I almost never want to allow a NULL value for a variable. NULL is so much worse than [] or ''. Think about concat(). When the trivial members of a type support most of the same behaviors as the nontrivial ones, that makes error checking so much easier.
Of course you're right! That statement was intended to be hyperbolic. I'm not actually suggesting that it would be a good idea to NOT have zero-sized collections.
I'm just exasperated that most of these modern, statically typed languages, give us TWO ways to write "nothing" (null and empty) and ZERO ways to write "must have something".
You COULD, theoretically, write a concat() that takes multiple, nullable, non-empty strings and returns a nullable non-empty string. Of course that's not ideal and would be horribly unergonomic. But you COULD do it. But how do you write a concat() that statically guarantees that if any of its arguments are non-empty that its output will be non-empty? You pretty much don't.
Don't get me wrong, I mostly like working Kotlin. But the more I do, the more I realize what a hodgepodge of features it really is. A bunch of features don't really work together that well. And a bunch of features are only 80% (or less) of what I actually want.
For example, what if I don't like data class's stupid copy() method? How do I opt out? You can't.
Value classes can't be used as varargs, can't implement an interface by delegating to the wrapped value. (Forgetting the fact that they're just buggy as all hell and I keep getting runtime crashes from them being overly aggressively optimized away)
And it, of course, inherits a ton of badness from Java. Like the crappy type-erased generics, lack of type classes, etc. It chose to just use Java's standard collection types and encourage copies instead of using persistent collections by default. It hides the mutability under "read only" interfaces, but that's not at all concurrency-safe. Map is not a Collection or an Iterable, which is dumb. Map<K, V>'s type parameters are also allowed to be nullable types, which means that Map.getOrElse{} is actually wrong in the standard library.
Since Kotlin chose nullable types instead of Option<T>, you can't express "nested" emptiness. This means that the Map API is janky if you need to store nullable types. Map.get returns null if there was no value stored, but what if the value stored IS null? Then you have to call Map.contains(key), which means you have to hash the key twice to reliably pull out values.
Belated thank you. I've been chewing on your observations. You're absolutely right about the papercuts.
I have to ponder the (lightweight) value objects stuff. And
> you can't express "nested" emptiness
Spot on.
I dislike all the current null mitigations: nullability (question marks), @nullable, Optional<?>.
My hobby language takes a different path. All nulls are actually Null Objects (under the hood). So method chaining cannot break.
re: type classes
Ya, a nice reminder that I need to learn Haskel. Stuff like that is still beyond me. I hope innovators continue to noodle with more accessible Haskel, Ocaml, etc.
Being late in my career, I'm motivated to create a better "blue collar" programming language for 99% of the work I've done. Meaning data processing. Ingest data, mostly strings, munge stuff, spit out results.
Statically typed languages give you u8/i8 types of numbers.
Maybe having a non empty string, or non empty list, type is useful now and then, but in practice, just have your code work just as well on both empty and non empty values, and you're good to go.
I'm pretty happy with the languages we have today overall (Kotlin and Rust at the top, but C#, Swift, and Java get an honorable mention).