Dang is a totalitarian liberal thought policeman. Hope you’re a San Francisco liberal or your opinions are banned. You’re free to express anything as long as you agree with his view of the world which, of course, must be the correct one.
This would be a nice feature for HN. Any links which have been previously discussed show up as a link with the total number of comments/votes also shown next to the link.
Edit: I noticed the 'past' link, but that takes you to the search engine. I'd prefer something more direct like what @dang just posted.
Isn't this basically the Python data model? Especially so in Python 3, where strings are strings of unicode codepoints rather than bytes ("runes" like Go would be even better but codepoints are halfway there).
Edit: In Python, equality, addition, even attribute setting can all be overridden by implementing certain "magic" methods on a class. Much of Python syntax is effectively syntactic sugar for calling these methods.
Swift probably inherits that from NeXT/OpenSTEP/Cocoa which was very unusual at the time in its degree of unicode support. For example it used something like unicode equivalence in string comparisons.
(You'd see objective-c benchmarks suffer because of this)
Python or hundreds of other languages have "everything is an object". Java is rare in having a small handful of non-objects in a mostly-object-oriented language.
Notice how big a problem the string/bytes split in Python 2 -> 3 migration was though - probably bigger than anything Java has had to do - which suggests this wouldn't necessarily have solved all problems as the author would like.
Clickbait in some respects. "Java should have been a different language from what it was, more pure in OO terms, and without the characteristics that made it understandable & popular for developers from C and similar backgrounds."
While the ideas are (somewhat) valid, it would have been a quite different language from the language that gathered traction and became popular as Java.
> While the ideas are (somewhat) valid, it would have been a quite different language from the language that gathered traction and became popular as Java.
Yep. Practicality beats purity when coding in industry, and the Java ecosystem has almost always chosen practical solutions over ideal ones.
This is the intentional result of prioritizing practicality and usefulness and portability.
People who write articles complaining about Java seem to think Java should have different priorities, but Java has been one of the most popular languages in industry for a long time because its priorities make sense.
>Clickbait in some respects. "Java should have been a different language from what it was, more pure in OO terms, and without the characteristics that made it understandable & popular for developers from C and similar backgrounds."
The description above is more of a strawman than the title is clickbait.
There's nothing in the post that would make Java less "understandable & popular for developers from C and similar backgrounds".
If anything, the simplifications proposed would make it more coherent and understandable.
>While the ideas are (somewhat) valid
What part of the ideas you don't find valid?
>it would have been a quite different language from the language that gathered traction and became popular as Java.
Not in any substantial way except that it would have more consistency in the treatment of value types, and 90% of it behind the scenes. Everything else, from a syntax, language, and API design perspective would be the same.
(Java with Generics, streams, closures, etc. today, and Value Types soon, is widely different already from the Java of 1997).
Java is a wildly successful language, and I find the title to be rather clickbait-y in that it attempts to be polarizing for no reason. Original sin implies a failure, and the author (of the blog) discusses a nuanced design choice of the language. It is one the most successful languages I know in terms of adoption and scale (alongside Javascript, C++, and Python).
By what measure, if not this one, can we truly compare languages?
> Now, if characters were objects, their representation would be encapsulated, and nobody would very much affected how many bits are needed.
Making characters objects doesn't seem like it magics away this issue. You'd need APIs to get and set raw values somewhere, and those APIs would have become the problem.
> Making characters objects doesn't seem like it magics away this issue. You'd need APIs to get and set raw values somewhere, and those APIs would have become the problem.
You never need to "get and set raw values". You need APIs to do the things that are sensible to do with characters, such as "test whether it has this property" or "encode as UTF-16 byte sequence", but all those things have much clearer semantics than "access raw value".
I had to specify the encoding because an unassisted Fixnum#chr works only for ASCII values. However I can imagine that it could use some intelligence to figure out the correct encoding. Probably it's not worth it and it could make mistakes.
Exactly - "turn this value from this specified encoding into a character" is semantically much clearer than "make a character whose internal representation is this integer".
You need raw values for serialization over the wire and to files. And for any kind of compression. And for searching (it's hard to build indexes in Unicode because of uneven symbol widths).
You could convert to a different character format but at some point you need raw values. If you want to be able to read text created by something else (text editor, browser, different OS) you have to expose raw access.
> You need raw values for serialization over the wire and to files. And for any kind of compression.
You need to encode as bytes with a particular encoding. That's not the same thing as "getting the raw value".
> And for searching (it's hard to build indexes in Unicode because of uneven symbol widths).
It may be hard but it's important to do it right. If searching for "café" doesn't find "caf◌́e" then you've got a problem.
> You could convert to a different character format but at some point you need raw values. If you want to be able to read text created by something else (text editor, browser, different OS) you have to expose raw access.
Again, no. You need to be able to decode byte sequences that have particular encodings into strings of characters. That doesn't mean you need to expose the internal representation of those strings/characters.
Nonsense. What is it that you can do with a "raw value" that you can't do with the representation in a particular encoding? I mean, if you wanted to import a character that doesn't have a unicode codepoint then you couldn't decode that character from UTF-8 - but if the language is built with no support for non-unicode characters then even if you did have access to the internal representation of a character, that wouldn't help you (e.g. the language's built-in character functions for things like checking the case of the character won't handle a non-unicode character properly).
In Java at least, you can write your own Charset implementation then the language will support it normally. This uses the byte raw access I'm ranting about to work
Yes and no - a Java Charset is something that can convert from a buffer of bytes to a buffer of utf16 code units. In Java that happens to be the internal representation of a String, but it doesn't have to be - you just need built in support for encoding/decoding a string as utf16.
A simple proof of this is that you can write custom character sets in Python too, even though there's no way to have raw byte access to a Python character (because it's different on different platforms/builds).
You could ask the API to get the UTF-8 or UTF-16 encoding. You could ask the API to get the unicode character id. But how it’s stored internally, you’d never care
I had to convert some strings to Base85 some years ago to integrate with an ancient system. There's no way to support all the different character sets out there "out of the box". Unless you want a crippled language you need to support access to raw values so people can write their own handler
What did you convert to Base85? The UTF-8 representation? The Unicode character ids, as 32-bit, appended after another (UTF-32)? The UTF-16 encoding? Big or Little Endian? Or with BOM?
There are no raw values.
The same string, used in Qt/C++ vs. stdlib/C++ results in different raw values, because it’s encoded and handled differently.
The fact that you believe there is a single true "raw value" of a string already shows why it’s important to make this explicit, because your code will break if used in different environments, and definitely won’t return the correct result.
Java does have quite nice classes for doing that sort of thing, but those classes need to be able to get and set the raw values somehow.
And, of course, users may need to convert their own binary formats to strings (compression and the like). At some point you'll be dealing with raw values.
You need the ability to convert between characters and some standard binary representation of them. But the language doesn't need to use that same binary representation as its internal representation, or expose the internal representation at all.
when I read an article like this and come to different conclusions, it makes me question if I understood it correctly.
I have been bitten more than most by the 16-bit char. I completely agree with the modern popular opinion that strings should be made from grapheme clusters, or failing that, codepoints.
But I get confused when the author uses the example of int, and then we run into problems. One confusion I have is how the author says that a method naming scheme is not operator overloading, when actually that's how it is always done in the languages I can name. Another confusion I have is that if primitive int and Integer merge into Int, then we have problems with null?
I've been a bit involved with the Mill CPU effort, and lets not go off on a tangent about that, but one thing in there that is a thing of beauty is the Not-A-Result/None approach. Values have special meta-data to say if they are an error or not. So, say, dividing by zero returns a value, but that value is tagged as an error, rather than an exception being thrown. If you use this value in subsequent computation, then the error propagates. Only if you then try and do something externally visible will the error throw. 'None' is a special type of error that doesn't throw when it becomes invisible, but instead becomes a no-store. This model is excellent for the speculation inside CPUs, but I posit that it would also be a really awesome model for higher level languages too.
Imagine modelling null as 'None'; a function returns a number, except it returns None instead. This None is then used in subsequent math, and the Noneness propogates. When you try and do something with it, it just silently does nothing. Another time a function returns an overflow error, or something like that, and when you eventually try and write that to a member variable where it becomes visible to other code, there's an error thrown. I know first hand that when you internalize this, lots of error handling and checking evaporates and its a nice zen.
Personally, I wish I had a faster and statically typed Python. I love Python's infinite integers and its string handling (which I had no problem with in python2 either, so ymmv). My biggest gripe with Python in production is that it isn't statically typed.
> Another confusion I have is that if primitive int and Integer merge into Int, then we have problems with null?
The author isn’t suggesting that this would be a design change that would be compatible with the current semantics of Java, so behaviour for null would change.
> One confusion I have is how the author says that a method naming scheme is not operator overloading, when actually that's how it is always done in the languages I can name.
I think the author is drawing a distinction between allowing custom operators with custom precedence, versus allowing custom implementation of a fixed set of operators with fixed precedence. I'd agree that it's a poor choice of terminology, since we would generally call the latter "operator overloading".
> Personally, I wish I had a faster and statically typed Python. I love Python's infinite integers and its string handling (which I had no problem with in python2 either, so ymmv). My biggest gripe with Python in production is that it isn't statically typed.
Yeah, I loved Python until my codebases became too large to refactor safely. I've found Scala gives the best of both worlds - the mixed object/functional style is quite Python-like and the low-ceremony syntax can be used to look quite Python-like (if you avoid libraries that use a bunch of symbolic method names) - though I wish infinite integers were the default there too. (I can write literals as e.g. bi"123789890897094790" and use normal operators on them, so it's not the worst).
> Personally, I wish I had a faster and statically typed Python.
Ever tried Crystal? It is more a statically type Ruby, but perhaps it would be close enough.. It don't have integers that automatically expand to bigger sizes though - it is in the process of at least getting errors on overflow on the other hand.
It also don't follow your idea for the None type, but instead have union typing, meaning that a nilable integer has a type Int32 | Nil (assuming 32bit size of the integer).
To me, the sin of Java has been to make String final.
Or, to be more precise, not to have made it generifiable.
(So the compiler knew the difference between, let's say, String<FirstName> and String<LastName>)
Something like semantic-aware String, yes.
(with syntactic sugar to avoid me repetitive tasks, plus the ability to uplift an existing code using String to its typesafe version in the most straightforwatrd manner). Something like the generics system but for String.
I of course can uplift my String into Objects. But it is unneeded for 90% of the String usages. Plus it forces you to unbox the String from its Object to pass it to a function that consumes just a String.
If you're just looking for a cleaner type system, you might give Kotlin a look first. It gets you that, while remaining a much smaller and simpler language than Scala.
Kotlin is a much larger than Scala on syntax, has 100+ keywords and built-ins, compiles slower, all while having a way weaker OO system and and doing a small fraction of what Scala does.
I haven't made a formal study, but I think that it still ends up still being a smaller language. Scala doesn't have a lot of keywords, but it does have a lot of overloading of what those keywords do. It's something I have to spend quite a bit of time explaining when I'm bringing people up to speed on Scala.
The OO system is absolutely weaker. Depending on your situation, that can be a big advantage. I work in a mixed Java/Scala codebase, and, while I generally like Scala, one of our more annoying bits of yak-shaving is making sure outward-facing Scala code doesn't do anything to make itself overly awkward to consume from Java. I haven't worked as extensively in Kotlin, but, from what I've seen, you've got to spend a lot less time worrying about it there, because Kotlin stays much closer to Java's OO semantics.
My sense is, if type erasure or lack of parametric polymorphism are really causing you pain, yes, Scala is for you. Pattern matching, too. If your Java pain points are more prosaic than that, Kotlin is a less radical change that is likely to address most of them them without introducing too many new ones.
67 comments
[ 2.2 ms ] story [ 129 ms ] threadPlease let me know what to think dang.
Edit: I noticed the 'past' link, but that takes you to the search engine. I'd prefer something more direct like what @dang just posted.
Edit: In Python, equality, addition, even attribute setting can all be overridden by implementing certain "magic" methods on a class. Much of Python syntax is effectively syntactic sugar for calling these methods.
Go runes are codepoints.
I think Swift is interesting, a "character" in Swift is actually a grapheme cluster.
(You'd see objective-c benchmarks suffer because of this)
I just wrote my first Go program this past weekend, evidently I misunderstood the tutorials/docs I read.
Notice how big a problem the string/bytes split in Python 2 -> 3 migration was though - probably bigger than anything Java has had to do - which suggests this wouldn't necessarily have solved all problems as the author would like.
While the ideas are (somewhat) valid, it would have been a quite different language from the language that gathered traction and became popular as Java.
Yep. Practicality beats purity when coding in industry, and the Java ecosystem has almost always chosen practical solutions over ideal ones.
This is the intentional result of prioritizing practicality and usefulness and portability.
People who write articles complaining about Java seem to think Java should have different priorities, but Java has been one of the most popular languages in industry for a long time because its priorities make sense.
The description above is more of a strawman than the title is clickbait.
There's nothing in the post that would make Java less "understandable & popular for developers from C and similar backgrounds".
If anything, the simplifications proposed would make it more coherent and understandable.
>While the ideas are (somewhat) valid
What part of the ideas you don't find valid?
>it would have been a quite different language from the language that gathered traction and became popular as Java.
Not in any substantial way except that it would have more consistency in the treatment of value types, and 90% of it behind the scenes. Everything else, from a syntax, language, and API design perspective would be the same.
(Java with Generics, streams, closures, etc. today, and Value Types soon, is widely different already from the Java of 1997).
By what measure, if not this one, can we truly compare languages?
He's also one of the team behind Dart. Hrm.
Consider that term in it's original context, and the size of the human population. It's clear that "original sin" does not imply failure to thrive.
Making characters objects doesn't seem like it magics away this issue. You'd need APIs to get and set raw values somewhere, and those APIs would have become the problem.
You never need to "get and set raw values". You need APIs to do the things that are sensible to do with characters, such as "test whether it has this property" or "encode as UTF-16 byte sequence", but all those things have much clearer semantics than "access raw value".
You could convert to a different character format but at some point you need raw values. If you want to be able to read text created by something else (text editor, browser, different OS) you have to expose raw access.
You need to encode as bytes with a particular encoding. That's not the same thing as "getting the raw value".
> And for searching (it's hard to build indexes in Unicode because of uneven symbol widths).
It may be hard but it's important to do it right. If searching for "café" doesn't find "caf◌́e" then you've got a problem.
> You could convert to a different character format but at some point you need raw values. If you want to be able to read text created by something else (text editor, browser, different OS) you have to expose raw access.
Again, no. You need to be able to decode byte sequences that have particular encodings into strings of characters. That doesn't mean you need to expose the internal representation of those strings/characters.
You keep reiterating this, but it's not feasible. To avoid exposing the raw values, the language would need to support all possible encodings.
A simple proof of this is that you can write custom character sets in Python too, even though there's no way to have raw byte access to a Python character (because it's different on different platforms/builds).
There are no raw values.
The same string, used in Qt/C++ vs. stdlib/C++ results in different raw values, because it’s encoded and handled differently.
The fact that you believe there is a single true "raw value" of a string already shows why it’s important to make this explicit, because your code will break if used in different environments, and definitely won’t return the correct result.
And, of course, users may need to convert their own binary formats to strings (compression and the like). At some point you'll be dealing with raw values.
Remember this decision was made in early 90's and getting users onto the language was imperative. Target audience was most likely using C at the time.
I'm actually curious to hear why the author thinks this, because I've largely come to the opposite conclusion…
https://dart.dev/faq#q-why-are-generics-covariant
I have been bitten more than most by the 16-bit char. I completely agree with the modern popular opinion that strings should be made from grapheme clusters, or failing that, codepoints.
But I get confused when the author uses the example of int, and then we run into problems. One confusion I have is how the author says that a method naming scheme is not operator overloading, when actually that's how it is always done in the languages I can name. Another confusion I have is that if primitive int and Integer merge into Int, then we have problems with null?
I've been a bit involved with the Mill CPU effort, and lets not go off on a tangent about that, but one thing in there that is a thing of beauty is the Not-A-Result/None approach. Values have special meta-data to say if they are an error or not. So, say, dividing by zero returns a value, but that value is tagged as an error, rather than an exception being thrown. If you use this value in subsequent computation, then the error propagates. Only if you then try and do something externally visible will the error throw. 'None' is a special type of error that doesn't throw when it becomes invisible, but instead becomes a no-store. This model is excellent for the speculation inside CPUs, but I posit that it would also be a really awesome model for higher level languages too.
Imagine modelling null as 'None'; a function returns a number, except it returns None instead. This None is then used in subsequent math, and the Noneness propogates. When you try and do something with it, it just silently does nothing. Another time a function returns an overflow error, or something like that, and when you eventually try and write that to a member variable where it becomes visible to other code, there's an error thrown. I know first hand that when you internalize this, lots of error handling and checking evaporates and its a nice zen.
Personally, I wish I had a faster and statically typed Python. I love Python's infinite integers and its string handling (which I had no problem with in python2 either, so ymmv). My biggest gripe with Python in production is that it isn't statically typed.
The author isn’t suggesting that this would be a design change that would be compatible with the current semantics of Java, so behaviour for null would change.
I can think of two main uses for Integer: when you have type polymorphism (e.g. my own code often has maps of Object) or nullness.
I go on to describe a different approach to nullness that suites ints, for example.
With mypy (http://mypy-lang.org/), it could be.
I think the author is drawing a distinction between allowing custom operators with custom precedence, versus allowing custom implementation of a fixed set of operators with fixed precedence. I'd agree that it's a poor choice of terminology, since we would generally call the latter "operator overloading".
> Personally, I wish I had a faster and statically typed Python. I love Python's infinite integers and its string handling (which I had no problem with in python2 either, so ymmv). My biggest gripe with Python in production is that it isn't statically typed.
Yeah, I loved Python until my codebases became too large to refactor safely. I've found Scala gives the best of both worlds - the mixed object/functional style is quite Python-like and the low-ceremony syntax can be used to look quite Python-like (if you avoid libraries that use a bunch of symbolic method names) - though I wish infinite integers were the default there too. (I can write literals as e.g. bi"123789890897094790" and use normal operators on them, so it's not the worst).
Ever tried Crystal? It is more a statically type Ruby, but perhaps it would be close enough.. It don't have integers that automatically expand to bigger sizes though - it is in the process of at least getting errors on overflow on the other hand.
It also don't follow your idea for the None type, but instead have union typing, meaning that a nilable integer has a type Int32 | Nil (assuming 32bit size of the integer).
The work on that project has been impressive, and there is now an experimental EA build called LW2 where you can play with value types: https://wiki.openjdk.java.net/display/valhalla/LW2
Will this solve everything? No. The char type is hosed. But there will be an opportunity to e.g. get a new Character.
They learned from Java's terrible sins. Features include value types (you can even use them as generic params!) And no type Erasure.
Besides massive improvements the language is nearly identical to Java. Only reason I'm still in Java land is the huge ecosystem.
It's disconcerting and astonishing to check for true / false and get a NPE, to say the least.
One nice thing about Go is that strings are values and cannot be null.
But this actually would have been better in Java because the compiler could have delt with the inefficiency cause by the unnecessary overhead.
At implementation make it an object but at runtime it will be a scalar.
class FirstName extends String {...}
I of course can uplift my String into Objects. But it is unneeded for 90% of the String usages. Plus it forces you to unbox the String from its Object to pass it to a function that consumes just a String.
The OO system is absolutely weaker. Depending on your situation, that can be a big advantage. I work in a mixed Java/Scala codebase, and, while I generally like Scala, one of our more annoying bits of yak-shaving is making sure outward-facing Scala code doesn't do anything to make itself overly awkward to consume from Java. I haven't worked as extensively in Kotlin, but, from what I've seen, you've got to spend a lot less time worrying about it there, because Kotlin stays much closer to Java's OO semantics.
My sense is, if type erasure or lack of parametric polymorphism are really causing you pain, yes, Scala is for you. Pattern matching, too. If your Java pain points are more prosaic than that, Kotlin is a less radical change that is likely to address most of them them without introducing too many new ones.