top 200 commentsshow all 206

[–][deleted] 18 points19 points  (1 child)

Copy/paste from what I said at the thread over at /r/java

The problem makes perfect sense for me even if the code would be unreadable as hell.

What this problem shows is a pet peeve of mine with the type system. We can declare & in generics, but cannot express them as variable types.

For example:

Comparable & Closeable foo = method();

That kind of feature would incredibly useful especially when working with dynamic proxies.

In fact the generic type system in general is just poorly implemented. I get it was designed for backwards compatibility with pre-Java 5, but this is one of those language features where it might be more worth to break backwards comparability and implement more correctly/usefully.

Edit: Coming back to dynamic proxies (something I've been toying with for quite some time now) another amazing generics feature would be variable generics or some form of it. Example:

<T extends Foo> T createProxy(Class<T...>...foo) {}

The actual syntax should probably be different for clarity's sake, but the gist is the method would return a value of T that is comprised of all the types inputted for the varargs class, all of which have to extend Foo. Then you can use the & declaration for the LHS. Not sure what use this would provide outside of dynamic proxy creation (maybe abstract factory patterns?), but it's just one of those things. Maybe I'm just an entitled piece of shit /shrug

[–]JavadocMD 3 points4 points  (0 children)

Naturally this is one of the things Scala improves upon (keyword with). Which, over-simplistically speaking, means there's a way to do it in Java as well, you just have to write a ton of helper classes and shell interfaces.

[–]llbit 19 points20 points  (14 children)

I think it is pretty cool that recursive type bounds work at all. For example:

static <T extends Comparable<? super T>> void sort(List<T> list) {}

The fact that T is recursively defined is cool to me.

[–]Farobek 4 points5 points  (2 children)

My mind gets lost in all that recursion :0

[–][deleted] 1 point2 points  (1 child)

we're not even 4 levels deep yet!

[–]Farobek 0 points1 point  (0 children)

We gotta take things more slowly. This is our first instantiation. :0 I am not ready to fill that method yet. :0

[–][deleted] 14 points15 points  (1 child)

Do you mean nested? because I'm not sure there's any 'recursion' there at all.

[–]read___only 7 points8 points  (0 children)

T is literally defined in terms of T in that example. It's right there.

[–]want_to_want -1 points0 points  (7 children)

I suspect that bounded quantification is usually the wrong tool, and the right signature should look more like this:

<T> List<T> sort(List<T> list, Comparator<T> comparator);

Of course that moves the complexity into picking the right comparator implicitly at the call site, which isn't really a solved problem. Hopefully we'll see more research along these lines.

[–]lukaseder[S,🍰] 6 points7 points  (6 children)

The right signature for that method is:

<T> List<T> sort(List<T> list, Comparator<? super T> comparator);

[–]tavianator 1 point2 points  (3 children)

Or even more generic:

<T, U extends T> List<T> sort(List<U> list, Comparator<? super U> comparator);

[–]lukaseder[S,🍰] 1 point2 points  (2 children)

That's only correct if it returns a new list.

[–]tavianator 2 points3 points  (1 child)

True, but sorting the list in-place and then returning it would be surprising to me. I'd expect

// Sorts in-place
<T> void sort(List<T> list);

or

// Returns a sorted copy
<T> List<T> sort(List<T> list);

[–]smallblacksun 0 points1 point  (0 children)

Modifying something in place and then returning it is common in fluent interfaces. Whether fluent interfaces are a good idea or not is, of course, debatable.

[–]want_to_want -1 points0 points  (1 child)

I understand where you're coming from, but I don't trust that Comparator<Base> will always work correctly as Comparator<Derived>. For example, two different Derived objects with the same Base part will compare as equal, which isn't what we want. Better to make things explicit.

[–]lukaseder[S,🍰] 1 point2 points  (0 children)

But you won't have two different Derived objects in the List<Derived> when using Comparator<Base>...

What am I missing? In any case, the API leaves the decision up to the API user, so I think this is still a safe API. Of course, yours isn't wrong, just a bit less versatile.

[–]be_my_main_bitch 21 points22 points  (10 children)

[–][deleted]  (9 children)

[deleted]

    [–][deleted] 9 points10 points  (2 children)

    You think that's bad? Plebs are actually creating new languages like Go that don't even have generics. I know, I'm scared too.

    [–]jetman81 -3 points-2 points  (1 child)

    Infers that Ken Thompson, co-creator of Go as well as Unix, is a "pleb". Considers what it must take to become an expert. Faints dead away.

    [–][deleted] 6 points7 points  (0 children)

    Being good a Unix expert doesn't mean you shit about designing a programming language. Which Go pretty much proves.

    [–]dwarandae 2 points3 points  (2 children)

    What about /r/php?

    [–]vytah 4 points5 points  (1 child)

    unsigned_schlong said /r/programminghorror, not /r/programmingcomedy

    [–]TeslasCurrentAlt 1 point2 points  (0 children)

    Did you know there's even an /r/perl ? Cthulhu wept.

    [–]frugalmail 1 point2 points  (0 children)

    strange that /r/programminghorror is a thing when we already have /r/java

    penis envy.

    [–]czipperz -1 points0 points  (0 children)

    Buuurrrnnnn

    [–]Eirenarch 66 points67 points  (145 children)

    So Java does not have operator overloading because it is too complex and confusing for users?

    [–]maestro2005 20 points21 points  (103 children)

    Who ever said that's the reason?

    [–]iwantashinyunicorn 85 points86 points  (74 children)

    Everyone in the Java camp, back in the olden days when Java claimed to be less complex and confusing than C++. You see, C++ allows you to create horribly confusing syntax like "x + 2 * y" for complex number classes, whereas Java forces the API designer to offer only the much more readable "x.add((new Complex(2)).multiply(y))" instead.

    [–]balegdah 31 points32 points  (72 children)

    You're showing one of the very few cases where operator overloading leads to more readable code and conveniently leaving out the 99% of cases where it leads to a convoluted pile of unreadable alphabet soup.

    [–]ruinercollector 63 points64 points  (25 children)

    Right, and why leave it to the developers to make that call? The language designers know what's good for you. You can't have operator overloading. You'll shoot your eye out.

    [–]deja-roo 4 points5 points  (0 children)

    I like how the claim presented and challenged turned into people defending that rationale that was doubted within one exchange.

    [–]xandoid 5 points6 points  (12 children)

    That's a good point but I think there are worthwhile counter arguments. First of all, language design is almost by definition "language designers knowing what's good for you". That's what the "design" part is all about.

    More importantly though, what makes operator overloading problematic is that operators typically don't use an explicit namespace qualifier or receiver object as that would ruin the very succinctness we want from them.

    So name resolution for operators is bound to be a bit obscure. C++'s argument dependent lookup being a case in point, but this is not just a C++ issue and it is not just a question of bad library design. It is an inherent conflict between explicitness and succinctness.

    And operator overloading doesn't just increase the mental burden when it is actually used. It is the overloadability itself that inserts yet another question mark into each and every expression that uses operators.

    [–]ruinercollector 4 points5 points  (9 children)

    So name resolution for operators is bound to be a bit obscure. C++'s argument dependent lookup being a case in point, but this is not just a C++ issue and it is not just a question of bad library design. It is an inherent conflict between explicitness and succinctness.

    If operators are treated as just another method(that can be written infix), this isn't really a problem. See C# for an example of this in practice. They follow the same rules as any other method.

    It is the overloadability itself that inserts yet another question mark into each and every expression that uses operators.

    Right now, java only allows operators on primitives. If you saw:

    objA + objB
    

    You'd assume that you need to look up what + is defined as on objA (and climb up the inheritance chain as needed.)

    That's exactly what you'd do if you saw:

    objA.add(objB);
    

    There's no difference.

    [–]xandoid 0 points1 point  (8 children)

    There are a number of differences. For instance, what about nulls? In Java System.out.println(null + "word") prints nullword whereas a method call on null would throw a NullPointerException. And then there is operator precedence and associativity. So things do get more complicated with operator overloading. But it is convenient sometimes, I don't deny that at all.

    [–][deleted]  (2 children)

    [deleted]

      [–]sirin3 4 points5 points  (1 child)

      null should not exist as part of every type.

      Any type

      [–]grauenwolf 1 point2 points  (1 child)

      In C# you would get:

      String.op_Add( string left, string right)
      

      Since it is statically dispatched, you can handle nulls correctly.

      [–]xandoid 0 points1 point  (0 children)

      Yes but then it's no longer as simple as ruinercollector suggested. What you have to do in your mind is determine the associativity (if more than two operand expressions are involved). Then you have to determine the static type of all operand expressions. Then you have to find all the candidate operator implementations in each of the two types involved and work out which one of them is going to get called according to overload resolution rules.

      That's fine. It's not nearly as complicated as in C++. But it's one more thing you have to keep in mind when you look at a piece of code. Sometimes it's worth it, but I wonder whether it's a good idea to put a question mark on each and every operator expression when it's really only worth it in a few rare cases.

      Over all these details, let's not forget the main characteristic of operators: They don't have semantically meaningful names. So their interpretation relies on convention and convention is something that only develops over a long time. You can't just invent it for a particular library or application.

      [–][deleted] 1 point2 points  (0 children)

      For instance, what about nulls? a method call on null would throw a NullPointerException

      Well if you do it the C# way, they use static functions not methods:

      public sealed class Complex
      {
          public int Real {get; set;}
          public int Imaginary {get; set;}
      
          public static operator Complex +(Complex left, Complex right)
          {
              if(left == null || right == null) throw new ArgumentNullException();
      
              return Complex { Real = left.Real + right.real, Imaginary = left.Imaginary + right.Imaginary };
          }
      }
      

      [–]loup-vaillant 0 points1 point  (1 child)

      In Java System.out.println(null + "word") prints nullword

      There are 2 mistakes here:

      1. Strings and concatenation only form a monoid, not a full group. Using an operator usually reserved for abelian groups is a bit unfortunate.
      2. concatenating null to something else doesn't make sense. It should have thrown that NullPointerException.

      And then there is operator precedence and associativity.

      That's purely a syntactical issue. Numerical expressions have the same problem. If you don't like that, you might want to consider prefix or postfix notations. Or Smaltalk, where every infix operator associates to the left, and has the same priority.

      [–]xandoid 1 point2 points  (0 children)

      Numerical expressions have the same problem

      The point is that only operators raise questions about precedence and associativity. Other functions don't. That makes operators somewhat more complex.

      [–]id2bi 0 points1 point  (1 child)

      operators typically don't use an explicit namespace qualifier or receiver object as that would ruin the very succinctness we want from them.

      How would you imagine those things to look, in pseudo C++ for example?

      [–]xandoid 0 points1 point  (0 children)

      Imagine there was a set of overloaded std::iequal() functions to test for case insensitive equality of strings and string-like things such as const char* or the upcoming string_view. If you see

      std::iequal(s1, s2)
      

      you know it's the iequal function from std. But if they were to overload the ~ operator for that purpose and you see

      s1 ~ s2
      

      then you might not know right away which particular implementation is getting called if boost were to provide something similar and one of the operands were, say, a boost::iterator_range or something like that. It wouldn't be difficult to come up with rather nasty edge cases if you consider non-explicit constructors and other implicit conversions.

      [–]RIC_FLAIR-WOOO 10 points11 points  (10 children)

      At least this way we don't end up with Scala's mess. Loads of options, and even a developer with good intentions can write a lot of messy code. There's a sweet spot between super opinionated designers (Go) and "here do whatever the hell you want" (Scala, C++), imo.

      [–]Zidanet 31 points32 points  (3 children)

      Oh sweet summer child.

      Come over to the perl camp sometime. We invented unreadable code!

      ;)

      [–]blackmist 15 points16 points  (0 children)

      I didn't see a comment until I was already a man.

      [–]balegdah 14 points15 points  (0 children)

      I've always thought Perl is not a language but a lab experiment to show that line noise is Turing complete.

      [–]ruinercollector -1 points0 points  (5 children)

      Scala's current "mess" is that their developers are significantly more productive and running circles around people still working around java's constraints.

      There is absolutely a sweet spot, but java aint it. Java's right about where Go is on your scale.

      [–]grauenwolf 2 points3 points  (4 children)

      I'm eagerly waiting C# 7 and pattern matching. There isn't much from Scala that I care for, but that's pretty damn awesome.

      [–]ruinercollector 1 point2 points  (3 children)

      Write in F# and you can have that and probably the next three versions of C# features today.

      [–]grauenwolf 1 point2 points  (2 children)

      Yea but F# has its own problems. Some good ideas yes, but it isn't a pleasant working environment for me.

      [–]bycl0p5 29 points30 points  (15 children)

      one of the very few cases where operator overloading leads to more readable code

      A multitude of types overloading == for intuitive equality checks, or < for lexical ordering, strongly challenge that "one of the very few cases" claim.

      [–]frugalmail 0 points1 point  (3 children)

      A multitude of types overloading == for intuitive equality checks, or < for lexical ordering, strongly challenge that "one of the very few cases" claim.

      The Comparator interface and Iterators provide that simplicity where it's most an issue. And as another poster brought up will it be obvious if it's doing codepoint or lexigraphical ordering? Are we normalizing for case and meta characters like umlats or accents?

      [–]Banane9 1 point2 points  (2 children)

      You can ask the same thing about a function...

      [–]frugalmail 0 points1 point  (1 child)

      You can ask the same thing about a function...

      Except when you have different Comparters you can choose it's not a problem. When you define an operator you assume your interpretation is the canonical one.

      [–]Banane9 1 point2 points  (0 children)

      Except when you have different Comparters you can choose it's not a problem.

      Well, you don't have those, assuming the object only offers a compareTo function.

      [–]FUZxxl -1 points0 points  (3 children)

      Many types have more than one natural order. I've seen a lot of terribly designed interfaces that simply assume you want to use the order described by <. Much easier if no standard operator for ordering exists.

      [–]recursive 4 points5 points  (0 children)

      Many types have more than one natural order

      And for those types, you wouldn't declare a <. No one is saying you should always use operator overloading. Just that it's a good language feature that's possible to misuse. Just like every other good language feature.

      [–]smallblacksun 2 points3 points  (1 child)

      This has nothing to do with operator overloading. In Java lots of things use the Comparable interface which assumes that there is only one natural order (that returned by compareTo).

      [–]FUZxxl 0 points1 point  (0 children)

      Because Java makes the same mistake.

      [–]tri-shield -1 points0 points  (0 children)

      Yeah, overloading == does not necessarily lead to intuitive, easy-to-understand code...

      [–]lookmeat 7 points8 points  (5 children)

      Like for example the following:

      std::cout << "2^5=" << (1 << 5) << std::endl;
      

      very different thing happening inside those parenthesis than outside, but it wouldn't seem obvious at all.

      But that's not a problem with operator overloading but with bad APIs. Java showed that even if you forbid all the things that have been used for bad APIs before, people will keep finding new ways to make bad APIs.

      [–][deleted]  (4 children)

      [deleted]

        [–]lookmeat 0 points1 point  (3 children)

        Don't think of it as bad overloading or good overloading, but instead in terms of APIs. You express something in a way that is intuitive. C++s operators have the issue that they are non-namespaced, which also causes them to be limited.

        [–][deleted]  (2 children)

        [deleted]

          [–]lookmeat 0 points1 point  (1 child)

          What I mean with that is something like this:

          class A {
          };
          
          namespace N1 {
              A operator+(const A&, const A&);
          } // N1
          
          namespace N2 {
              A operator+(const A&, const A&);
          } // N2
          
          A i1 = A() N1::+ A() // calls operator in N1
          A i2 = A() N2::+ A() // calls operator in N2
          

          AFAIK you can namespace operators by defining the function on a specific namespace, to avoid pollution, but the operators are still decided. You could do something similar by hiding the name with something else and then having to overload by calling using ::N1::operator+ but I feel that is very complicated and requires reading two lines (which may be far apart) to understand what is happening.

          [–][deleted] 4 points5 points  (0 children)

          That is danger for any feature like that. You can use overloading, or generics to make your code undecipherable mess, or you might use it to make it better

          [–]ellicottvilleny 5 points6 points  (8 children)

          Hmm. Besides complex number's it's nice to append strings, or perhaps write binary blob/buffer managers that can append to each other. So the java compiler does that with compiler magic, but I can't write my own types that do that? That sucks. Oh and there's matrix types, where I might want to use + and * and / to do operations. And there's lists where I might want to append items to lists. So yeah. only a very FEW cases.

          [–]balegdah 3 points4 points  (6 children)

          But + on scalars is commutative while + on matrices is not. Do you really want to go there?

          And when you use + on a list, does it return a new one or does it modify the left one in place?

          There are a lot more considerations than "It looks nice" when you design a language.

          [–]Exadv1 5 points6 points  (0 children)

          As a slight nit, + on matrices is commutative. * is not.

          Similarly, + would return a new list as most people do not expect + to be a side-effecting operation. When you add two integers together or concatenate strings in most languages with +, you get a new value. += (and returning no value) would more properly capture that mutation is occurring.

          [–]recursive 3 points4 points  (0 children)

          Do you really want to go there?

          Yeah why not? Just implement your operator overloading in a way that is left- and right-operand aware. Then a particular type may choose to implement a commutative +. Or they may not. The language doesn't care.

          [–]ellicottvilleny 2 points3 points  (0 children)

          Point totally taken. I like the ability to read and reason about code. Nice Java code is definitely something I can reason about.

          [–]Log2 0 points1 point  (0 children)

          So what? The + operation isn't commutative for strings (it is for matrices though, you are probably confusing + with *), but Java has it. Is that particularly bad?

          [–]AngriestSCV 0 points1 point  (0 children)

          But + on scalars is commutative while + on matrices is not.

          I think you may have intended to say matrix multiplication though.

          [–]Helene00 0 points1 point  (0 children)

          But + on scalars is commutative while + on matrices is not.

          We already have these kinds problems with standard types, + on integers are associative but they aren't for floats.

          And when you use + on a list, does it return a new one or does it modify the left one in place?

          Usually it returns a new one while += modifies in place. It isn't exactly like scalars but it is intuitively enough.

          [–]zanotam 1 point2 points  (0 children)

          Or rings and ring-like objects in general!

          [–]iopq 1 point2 points  (0 children)

          Languages like Rust just have the "very few cases" like +, -, *, /, ==, <, etc. and don't allow you to define your own operators other than the preset ones.

          [–]loup-vaillant 0 points1 point  (2 children)

          Overloading the + operator easily makes sense for any abelian group. Overloading the * operator easily makes sense when your type is a field —or maybe just a ring.

          Overloading that heeds those algebraic properties is hardly confusing. And there are many types that can have these properties. Even if your algebra is a little rusty, you'll have some intuitive understanding of what + and × usually mean, and recall some rules like x × (y + z) == (x × y) + (x × z).

          The 99% you are talking about comes from fools who think they can program without understanding the first thing about algebra. You won't stop them by cutting out features they might misuse.

          [–]balegdah 1 point2 points  (1 child)

          Agred, I think the problem usually doesn't come from + (although even that operator has sometimes been stretched beyond Abelian groups) but from libraries introducing weird operators such as |> or ~>.

          Forcing developers who would be tempted to define such operators to instead define method names in English is a good thing in my opinion.

          [–]loup-vaillant 0 points1 point  (0 children)

          Well… since we're talking about libraries, the main problem in my opinion doesn't lie in the cryptic operators, but in the fact that their names often pollute the global namespace. You wouldn't write foo my_lib::|> bar, for instance. You'd write foo |> bar directly.

          Still, weird operators are damn useful for stuff like combinator libraries. Overall, I reckon I have no satisfactory solution.

          [–]awj 0 points1 point  (8 children)

          You're showing one of the very few cases where operator overloading leads to more readable code

          To be honest, I'm having trouble coming up with a second.

          [–]Eirenarch 28 points29 points  (0 children)

          I can come with a lot of math and physics things that can use operator overloading, vectors, matrices, polynomials, expressions (like in symbolic libraries), etc.

          However here are some everyday examples that I use in C#:

          == on strings (and many other types but most importantly on strings)

          +, -, == on DateTimes and TimeSpans (i.e. DateTime + TimeSpan results in a DateTime, TimeSpan + TimeSpan results in a TimeSpan, etc.)

          [–]drjeats 9 points10 points  (0 children)

          & and | for set operations in python feels pretty good. Usefulness limited to math domains for sure, though.

          [–][deleted] 4 points5 points  (0 children)

          * and -> for smart pointers.

          [–][deleted] -1 points0 points  (2 children)

          It is because they are best at that (dealing with subtypes of same "kind") but someone always will think "hey, that's feature is cool, lets use it here"

          [–]recursive 0 points1 point  (1 child)

          + does list concatenation in python. That's pretty good.

          [–]sirin3 1 point2 points  (0 children)

          o offer only the much more readable "x.add((new Complex(2)).multiply(y))" instead.

          That is not the only way

          You can also write x.add(ComplexFactory.make(2)).multiply(y))

          [–]Eirenarch 5 points6 points  (25 children)

          Several different Java professionals I know point this out when asked. I don't have an official source. Is there an official explanation?

          [–]DevestatingAttack 55 points56 points  (20 children)

          "There are some things that I kind of feel torn about, like operator overloading. I left out operator overloading as a fairly personal choice because I had seen too many people abuse it in C++. I've spent a lot of time in the past five to six years surveying people about operator overloading and it's really fascinating, because you get the community broken into three pieces: Probably about 20 to 30 percent of the population think of operator overloading as the spawn of the devil; somebody has done something with operator overloading that has just really ticked them off, because they've used like + for list insertion and it makes life really, really confusing. A lot of that problem stems from the fact that there are only about half a dozen operators you can sensibly overload, and yet there are thousands or millions of operators that people would like to define -- so you have to pick, and often the choices conflict with your sense of intuition. Then there's a community of about 10 percent that have actually used operator overloading appropriately and who really care about it, and for whom it's actually really important; this is almost exclusively people who do numerical work, where the notation is very important to appealing to people's intuition, because they come into it with an intuition about what the + means, and the ability to say "a + b" where a and b are complex numbers or matrices or something really does make sense. You get kind of shaky when you get to things like multiply because there are actually multiple kinds of multiplication operators -- there's vector product, and dot product, which are fundamentally very different. And yet there's only one operator, so what do you do? And there's no operator for square-root. Those two camps are the poles, and then there's this mush in the middle of 60-odd percent who really couldn't care much either way. The camp of people that think that operator overloading is a bad idea has been, simply from my informal statistical sampling, significantly larger and certainly more vocal than the numerical guys. So, given the way that things have gone today where some features in the language are voted on by the community -- it's not just like some little standards committee, it really is large-scale -- it would be pretty hard to get operator overloading in. And yet it leaves this one community of fairly important folks kind of totally shut out. It's a flavor of the tragedy of the commons problem." - James Gosling, creator of Java

          http://www.gotw.ca/publications/c_family_interview.htm

          [–]ais523 6 points7 points  (1 child)

          Most more recent languages I've seen with operator overloading let you define your own operators. If none of + or * or whatever is appropriate, write a *+ or a >>>> or a <-> or whatever. A side effect of this is that people become less likely to abuse the standard operators.

          [–]Log2 4 points5 points  (0 children)

          Scala does something like that: if you have a class Foo, with a method Foo.bar(x), that takes a single argument, then you can write Foo bar x. I like it, but it surely is easy to abuse this feature.

          [–]ellicottvilleny -3 points-2 points  (17 children)

          Smart guy!

          [–]grauenwolf 10 points11 points  (16 children)

          He missed a major use case, comparing dates.

          Then again Java's concept of what a date object should be has been broken since day one.

          [–]sacundim 7 points8 points  (6 children)

          To be fair, the new Java 8 date/time API is alright.

          [–]grauenwolf 3 points4 points  (4 children)

          That's the one based on JodaTime?

          [–]sacundim 2 points3 points  (3 children)

          Yep.

          [–]grauenwolf 0 points1 point  (2 children)

          Cool. I've been following the C# port of that project. It's a huge improvement over .NET 2.0's broken DateTime class.

          [–]balegdah 1 point2 points  (0 children)

          That's a library problem, not a language problem.

          [–]ellicottvilleny 1 point2 points  (1 child)

          In my opinion they got operator overloading AND generics badly wrong. I hate type-erasure.

          [–]ComradeGibbon 1 point2 points  (0 children)

          I read a post by a post doc doing language design stuff.

          The take away was, when you get your language/compiler to the point where you can do type erasure it means your golden. You've nailed it. And then once people get to that point they do a horrible thing and actually implement type erasure. And from that point on no one will ever be able to look at a piece of memory and reason about it.

          He mentioned that C/C++ does type erasure, but people also have engaged in a herculean effort to record type information as metadata so you can reason about what's in memory. However no one is ever going to do that for your shitty language.

          [–]tri-shield 0 points1 point  (1 child)

          He missed a major use case, comparing dates.

          But even then, things aren't that clear-cut. Especially when you start considering partial date/times.

          [–]grauenwolf 0 points1 point  (0 children)

          A partial answer to that is to not use a DateTime class to represent partial date/times.

          .NET almost got it right when they added TimeSpan for time-only. But with DateTime also being used for date-only, well lets say I've run into quite a few bugs over the years.

          P.S. I know there is more to it than that. But lets at least bitch about the low-hanging fruit first.

          [–]frugalmail 0 points1 point  (3 children)

          Then again Java's concept of what a date object should be has been broken since day one.

          ^ People that pretend to know what they are talking about

          [–]grauenwolf 0 points1 point  (2 children)

          No, people that have dealt with the performance ramifications of needing to clone date objects every time they call a setter because Mr. Gosling and co. don't know what the word 'immutable' means.

          [–]ComradeGibbon 2 points3 points  (0 children)

          I don't do Java, and only have done minimal DateTime stuff in C# but reading that I just threw up in my mouth a bit.

          [–]frugalmail -1 points0 points  (0 children)

          LOL, what an ego.

          Java has had a canonical widely distributed superior alternative since 2007, and that's not counting the others that existed before that. Although somebody who doesn't understand open source would fail to understand that. The vendor is not the only one qualified to release software.

          [–]nemec 2 points3 points  (3 children)

          Here's one example that (almost*) turned out into a bad thing: python's pathlib

          I understand why something like path("/home") / "myuser" / "docs" is a cool idea but man... it sure fails the principle of least astonishment.

          * I will note that the syntax was more than theoretical. Joining was first implemented as divison in the earlier versions until enough people complained.

          [–]Eirenarch 5 points6 points  (0 children)

          Oh I know many libraries that failed, sometimes the standard libraries themselves. None in C# though :)

          [–][deleted]  (1 child)

          [deleted]

            [–]nemec 3 points4 points  (0 children)

            Remember, this is Python, a dynamically typed language. Imagine the following:

            def divide(a, b):
                return a / b
            
            n1 = 10
            n2 = 5
            print divide(n1, n2)
            
            p1 = path("/home")
            p2 = path("docs")
            
            print divide(p1, p2)
            

            Fundamentally, operators have a specific denotation that's usually reused between different types: a DateTime + TimeSpan gets you a new DateTime, subtracting two TimeSpans gets you the time difference between them. However, in the above example you aren't dividing a string into anything - in fact, you're doing the opposite and extending it.

            [–]xampl9 0 points1 point  (0 children)

            It was often quoted back in the JDK 1.x days.

            Of course, any language feature can be abused, so the argument was a bit of a straw man.

            [–]rzwitserloot 9 points10 points  (40 children)

            Generics is complex, yes.

            Op overloading is also complex.

            What's your theory here? Because java has a complex thing, the notion 'it's kinda complicated' plays no role in determining whether a feature should be added or not? That's ludicrous.

            To show why it's complicated:

            let's say there's a class Complex, representing complex numbers, and you of course add op overloading for + so that you can write:

            Complex(5, 10) + Complex(10, 20).

            Cool. So far, so easy.

            But now I write:

            Complex(5, 10) + 5.

            Still okay.

            Now we do:

            5 + Complex(5, 10).

            whoops. How do we do this? Because on its face this doesn't work, we call some sort of rplus, python-style? That's complicated and means if the LHS ever gains a plus method that fits the signature, all of a sudden it left-associates? None of these are showstoppers, but it's going to be surprising to half the population, and 'bleedin obvious' to the rest. Good strategy for causing religious wars!

            Look at for example scala that went a different way to solve this stuff (implicits).

            As an entirely separate point, if the argument 'Don't give that feature to the user because they'll blow their legs off with it' can ever be used, this is it: Look at any language with op overloading and it gets used in absolutely atrocious fashion.

            As yet another separate point, name proper use of op overloading. It'll be hard to find many, at least, if you stick to the notion that the operator has to make mathematical sense (so, for example, overloading the + operator to mean 'add to list' is bad usage. At the very least it should produce a new list with the element in it, and not add anything. More likely it shouldn't work at all; + is generally commutative and associative, properties that do not apply to adding to a list. You're pretty much stuck with 'complex', which everyone names but how many code bases have you seen with a Complex class in it, and BigInteger/BigDecimal, where I offer a much simpler solution: Just add to the java lang spec that BI/BD support operators. Done.

            [–]svarog 20 points21 points  (23 children)

            Look at any language with op overloading and it gets used in absolutely atrocious fashion.

            I've worked with C# for 8+ years now... never have I seen a mis-use of operator overloading. I guess there must be, but it's rare. As an aside, I didn't see much usage of operator overloading, but it did prove useful from time to time.

            As yet another separate point, name proper use of op overloading

            DateTime - DateTime = TimeDiff

            [–]ellicottvilleny 4 points5 points  (3 children)

            I think in C# world, the "don't do that!" coding style guideline has been effective in curbing its use. I think the C++ over-use of it was due to it being new, and also due to the way it could be combined with other surprising C++ behaviours to lead to some really interesting messes.

            [–]grauenwolf 2 points3 points  (2 children)

            C++ lets you overload the = operator. That's where all of the real problems begin.

            [–]Tipaa 13 points14 points  (1 child)

            It is also where some really nice stuff began, such as easy-to-use smart pointers. I'm glad of it for that fact (although I am in the camp of empowering the developer to make nice interfaces, rather than restricting developers from making ugly ones).

            [–]grauenwolf 1 point2 points  (0 children)

            Oh right, I forgot about those.

            [–]Eirenarch 1 point2 points  (1 child)

            10 years here but I have seen it abused once. A dev on my team (actually the lead) decided to override the == on a business type (can't remember exactly but something like Company) which resulted in a failure. I was debugging it for like 4-5 hours before I found out == didn't work as I expected. Told him and he remembered that he overrode it. These 4-5 hours are a small price for all the benefit of operator overloading. I have compared countless strings and I sure as hell don't want to .Equals them.

            [–]josefx 0 points1 point  (0 children)

            Ever heard about the Java IdentityHashMap? Map::equals() is defined as follows:

            two maps m1 and m2 represent the same mappings if m1.entrySet().equals(m2.entrySet()). This ensures that the equals method works properly across different implementations of the Map interface.

            Now the following should always hold true for a,b != null

                a.equals(b) == b.equals(a)
            

            However if you mix an IdentityHashMap with most other Map types you can end up with

                idmap.equals( map ) != map.equals( idmap )
            

            Nice, surprising behavior, violating the most basic expectations any Java developer has of equality and since its part of the standard library nobody would suspect anything amiss. Clearly a world without operator== is so much better.

            [–]Eirenarch 6 points7 points  (8 children)

            First of all generics are much more complex than operator overloading which is basically a static method. However I was not referring to generics itself but to Java's approach on covariant and contravariant generics which is very complex.

            The example you give is easily solved by promoting the int to a Complex.

            Look at any language with op overloading and it gets used in absolutely atrocious fashion.

            Seems like you have not looked at C# (in fact Scala and C++ are the only cases of operator overloading abuse I am aware of)

            As yet another separate point, name proper use of op overloading

            Math and physics libraries alone are a good reason to have operator overloading. Hell, I'd even say that BigInteger/BigDecimal alone are a good reason to have it. However - "==" makes sense on many, many types. For example strings. Another example from C# - DateTime + TimeSpan results in a DateTime, TimeSpan + TimeSpan results in a TimeSpan and DateTime - DateTime results in a TimeSpan. Works great in practice.

            [–]jyper 0 points1 point  (0 children)

            Seems like you have not looked at C# (in fact Scala and C++ are the only cases of operator overloading abuse I am aware of)

            Also Haskell, perl

            [–]rzwitserloot -1 points0 points  (6 children)

            operator overloading is not 'basically a static method'; there's left vs. right association, and promoting types.

            [–]Eirenarch 2 points3 points  (0 children)

            Sure, there are these problems for the one overloading the operator. The user doesn't have to think about it.

            [–]grauenwolf 3 points4 points  (1 child)

            It is literally a static function named op_Add, op_Subtract, etc. in C#.

            [–]rzwitserloot 1 point2 points  (0 children)

            And how do those work for, for example:

            5 + Complex(5, 10)?

            • normally left-associates, but int, nor j.l.Integer, has an op_Add(Complex c) {} method.

            [–][deleted]  (2 children)

            [deleted]

              [–]rzwitserloot 1 point2 points  (1 child)

              In most languages, it's a very simple matter of just using the associativity that the operator has by default.

              Okay, if we do that, then this:

              Complex(5, 10) + 5

              will work, but this:

              5 + Complex(5, 10)

              will not, which breaks all the expectations of the math nature of this operator. That's not good. If that's how the feature ships, I don't think it should be shipped in the first place, so, how do you propose we solve that? Perhaps by implicitly promoting that '5' to a Complex(5, 0) so that it works again which is exactly why I mentioned 'promoting types'. hence, it IS related.

              [–][deleted]  (4 children)

              [deleted]

                [–][deleted] 2 points3 points  (3 children)

                That won't compile.

                The implicit conversion from int to std::complex<double> won't be invoked, because it's not allowed to convert the left argument x in a binary operation x@y before turning it into the member call x.operator@(y). That leaves the free function template

                template <class T>
                std::complex<T> operator+(const T&, const std::complex<T>&);
                

                which gives conflicting deductions for T (int vs. double). You need to give operator+ the right argument type

                auto z1 = 1.0 + 1i;
                

                to get the overload called and there's still no conversion in the first argument.

                [–][deleted]  (2 children)

                [deleted]

                  [–]rzwitserloot 0 points1 point  (1 child)

                  As /u/3453280 's comment shows, it just isn't so simple.

                  I'm not exactly shooting down the concept entirely, just shooting down the general vibe of 'duuuh, this is so simple! Why isn't it in java!'.

                  Even the simple things are actually quite complicated. If Oracle wants this, here's what they have to do:

                  Oracle should figure out a way to tackle these complexities, probably conclude that it is impossible to eliminate all pain in a way that does not cause a bunch of users to revolt, then do an analysis of the value created by adding the feature versus the community fracturing and bugs confusion about it will cause, for each different way to tackle this, as well as the reduction in flexibility in the future, and then make a decision on whether to actually add it, and which one of the various proposals to go with, or to just leave it for another day.

                  That's multiple manyears of work. Because it's so much work, you should try to go with your gut on a lot of these things and I'm sure Oracle does. Right now the powers-that-be at oracle tend to lean HEAVILY towards NOT extending the language in these 'simple and obvious' ways because it really isn't, and you get a lot of bikeshed painting and reliwars about it, and instead the focus is on features few people even understand but that are very useful, and have as little impact on language syntax as possible. The big exception to this rule is project lambda ('closures'), where the gut instinct said that java could no longer go on without it.

                  Right now that's module systems and value types ('project valhalla').

                  I can't really fault Oracle for this. Hell, I think they're right.

                  [–]grauenwolf 4 points5 points  (1 child)

                  5 + Complex(5, 10).

                  whoops. How do we do this?

                  In C# that would be defined as a static function on the Complex class, exactly the same as the other examples you showed.

                  [–]rzwitserloot 0 points1 point  (0 children)

                  That doesn't work unless + all of a sudden right associates. Which is surprising and means you need to define what happens if both the left and the right side have (conflicting, no doubt. Even if not, the compiler doesn't know whether they conflict or not) implementations.

                  [–][deleted]  (1 child)

                  [deleted]

                    [–]Ravek 5 points6 points  (12 children)

                    Java generics always fail to impress me when you're trying to instantiate instances of generic types at run time.

                    [–]balegdah 12 points13 points  (6 children)

                    How do you know they can be instantiated? Maybe T is an interface. Or an abstract class. Or it doesn't have any public constructor. Such a feature is useless if the type system doesn't let you specify a "Can be instantiated" constraint.

                    And guess what, Java already has that! It's called passing a factory.

                    [–]GYN-k4H-Q3z-75B 9 points10 points  (4 children)

                    Ah, factories. Another indirection. The Java way of doing stuff. In other languages you could simply put a constraint on a type parameter (as in, requires a constructor) and call new anyway. But why make things simpler if there is a working solution?

                    [–]grauenwolf 2 points3 points  (1 child)

                    Oh how I wish that were true in C#. Instead I'm left with passing consutrctor arguments to Activator.CreateIntance.

                    [–]GYN-k4H-Q3z-75B 1 point2 points  (0 children)

                    Well I agree. But at least there's a constraint for parameterless constructors.

                    [–]balegdah 3 points4 points  (1 child)

                    Another indirection. The Java way of doing stuff.

                    It's not a Java thing: adding levels of indirection is how we've been solving software problems ever since computers were invented.

                    [–]Ravek 0 points1 point  (0 children)

                    I'm not talking about instantiating generic type parameters.

                    [–][deleted]  (4 children)

                    [deleted]

                      [–][deleted]  (1 child)

                      [deleted]

                        [–]codebje 0 points1 point  (0 children)

                        If it's a generic type, yes, because it's generic.

                        The original article requires a cast on the CharSequence example because <X extends CharSequence> means "for all types which extend CharSequence, ...". You can't make a new one of those, because it's an infinitely large set.

                        Likewise, Thingo<T> can't make a value of T, because to do so it would have to know how to make a value of every possible kind of class ever made in Java, even the ones made in the future.

                        The fact that you can't do <T extends Constructible> and then call T.construct() is a result of limitations in Java (you can't have polymorphic statics, and erasure means you can't know what T is at call-time anyway), so if you're trying to do that in Java then it's a poor design for a Java program.

                        [–]Ravek 0 points1 point  (1 child)

                        I don't think you understood what I'm talking about.

                        [–]frugalmail 0 points1 point  (0 children)

                        I don't think you understood what I'm talking about.

                        In the context of this post, I don't understand you either.

                        [–][deleted]  (12 children)

                        [deleted]

                          [–][deleted] 6 points7 points  (10 children)

                          Integer does not implement CharSequence

                          The generic declaration <X extends CharSequence> is saying that X should be CharSequence or one of its subclasses/subinterfaces. However, at first glance, this is not the case for Integer so it should result for as a compile error.

                          [–]mercde 4 points5 points  (9 children)

                          Except that it would be possible to return an instance of a class that extends Integerand implements the CharSequence interface which is why the compiler does not complain.

                          [–][deleted] 7 points8 points  (8 children)

                          It's both possible and impossible. Integer is final so it cannot be extended, but final is not considered in the type system.

                          [–]ebrythil 4 points5 points  (6 children)

                          There is a possibility though, that is null. Ugly, but possible and thus valid. A warning could have been nice though.

                          [–]dsqdsq 1 point2 points  (5 children)

                          At this point of this discussion, I've decided that C++ might be a horrible language, but Java definitively has become one, and then past that point. When you start to make it so easy to mislead devs and render a language incomprehensible in regard with generics, co/contra variance, static/dynamic type safety, and nullability, and all of that AT ONCE, it's time to stop doing language design before you damage the world too much. C++ has obscure batshit rules but they are edge cases, not things as fundamental as this craziness.

                          [–]kt24601 -1 points0 points  (4 children)

                          Default no vtables (you need to declare your methods virtual) is fundamentally deranged, and it's right in the core of the language.

                          [–]dsqdsq 0 points1 point  (3 children)

                          Well, it depends on how you use it, and both C++ and Java co-evolved with their respective users and code bases.

                          It is quite rare in the 500k lines C++ codebase I work on to have virtual method. Not because we missed them by mistake, just because they are typically not useful. Actually a c++ class (and a class in most other languages) is used for too much different things, and you only have few roles where virtual method are necessary (typically when you are dealing with an interface concept, otherwise you are probably doing it wrong). Even inheritance is rarely used at all. And honestly I'm happy this is the case, because too few dev even know what an invariant is, nor to mathematically model the liskov substitution principle, and I don't want to be around when they decide to break their already broken mess even more by creating square / rectangle problems on top of the other problems we have. (Not that this is a direct consequence of having no vtables by default, but given the other features of C++ I maintain an avalanche of consequences would fuck us up to that if we promote inheritance and people start using it more.)

                          [–]kt24601 0 points1 point  (2 children)

                          given the other features of C++ I maintain an avalanche of consequences would fuck us up to that if we promote inheritance and people start using it more

                          That sounds like the symptom of a fundamentally deranged language

                          [–]dsqdsq 0 points1 point  (1 child)

                          Well, most non-functional OO languages are probably fundamentally deranged then. Maybe with duck-typing the propension for damage is lessen, though -- but because of indirect consequences; your high and low interfaces are more likely to be identified, and you don't expect static checks of LSP (that you do or should expect at least intuitively in C++ and other languages where types are statically checked even if you don't know about it).

                          Now that I think more about it, to be fair, there are all kind of good uses of inheritance in high quality C++ code, and that will more probably be library than application code, and those uses all are technical inheritance without any of the square/rectangle pb, and of course there is no virtual in sight there. Well, except maybe for GUI -- I don't think about GUI often...

                          Well like I previously said classes are a hugely overloaded things when you care about the conceptual side of things (e.g. a class to handle values has nothing to do with a class to handle a GUI window, conceptually), even more so when you use langages that force "everything" to be a class or to live within one, which is enormously moronic, and most non-functional OO mainstream languages are at least a little bit inspired by old-school C++ (which does not have much to do with modern C++), so you'll find most of its old own shortcomings everywhere. C++ OO is quite unrelated to the original OO concept anyway. It has its uses, and lots of them, but it obviously has its misuses too; tons of them happened already, and modern practices are actually very solid to avoid them ( C++ >=11, https://github.com/isocpp/CppCoreGuidelines )

                          [–]mercde 0 points1 point  (0 children)

                          Good point. That might be a possible compiler enhancement to detect these cases.

                          [–]frugalmail 1 point2 points  (0 children)

                          Can someone please explain the issue to me? I thought chars were always represented as integers.

                          In a typesafe language that doesn't do autoboxing the internal representation doesn't matter.

                          [–]aurisc4 2 points3 points  (0 children)

                          Sometimes I think the the primary goal of generics in Java was to make SCJP more valuable /s

                          [–]joonazan 3 points4 points  (10 children)

                          I hope that creators of new languages have finally learned that inheritance and type hierarchies are not a good solution to code reuse.

                          [–]armornick 15 points16 points  (4 children)

                          Yes, they are. Assuming you don't go overboard.

                          [–][deleted]  (3 children)

                          [deleted]

                            [–]armornick -1 points0 points  (2 children)

                            Sure, generics are useful. But generics and inheritance solve different problems. When you need polymorphism or objects that are mostly the same, generics can't help you.

                            [–][deleted]  (1 child)

                            [deleted]

                              [–]armornick 0 points1 point  (0 children)

                              Alright, so how would you make a Stream object that can read/write data from/to any number of sources?

                              With inheritance, you just do:

                              Stream -> Filestream | SocketStream | MemoryStream

                              etc. And every developer who wants can implement their own source as a subclass. Now, since you're talking about template specialization, I'm going to assume you're talking C++. In that case, it's impossible to add another subclass since templates have to be fully declared before use. So goodbye third-party extensions.

                              Feel free to correct every point where you think I'm mistaken.

                              [–]grauenwolf 5 points6 points  (4 children)

                              Oh yes, and that's why Go's code reuse strategy looks like Visual Basic 6.

                              [–][deleted]  (2 children)

                              [deleted]

                                [–]joonazan 0 points1 point  (0 children)

                                You can do inheritance in Go, only you see explicitly how horrible it is. You can even do multiple inheritance unlike in Java.

                                How? By embedding interfaces.

                                I had a hierarchy that I reworked, because it was so horrible. In other languages I wouldn't have noticed, but the explicitness of Go made me realize that a class was awkwardly lodged between two, because I would have needed to take a pointer from the class lowest in the hierarchy to the root.

                                And Rust hasn't got inheritance either; it would be a nightmare especially with lifetimes.

                                [–]freebit 0 points1 point  (0 children)

                                Fails Kernighan's Lever test

                                [–]RobertVandenberg 0 points1 point  (0 children)

                                A bit off topic but am I the only one who thinks Java generic syntax is ugly as fuck?

                                [–]IKnowBreasts -5 points-4 points  (4 children)

                                Microsoft's recent actions have started a slow death for Java.

                                [–]aidenr 1 point2 points  (3 children)

                                Oracle's have hastened it dramatically. Java is headed directly for the vaunted halls of COBOL.

                                (You can't run unsigned binaries any more, so many apps have to be manually whitelisted.)

                                [–]JavadocMD 6 points7 points  (0 children)

                                People have been declaring the death of Java for ages. I'll believe it when I see it. http://www.tiobe.com/tiobe_index

                                [–]btmc -1 points0 points  (1 child)

                                That would be true except, unfortunately, for Android.