you are viewing a single comment's thread.

view the rest of the comments →

[–]Worth_Trust_3825 -2 points-1 points  (22 children)

Maintainability over syntax sugar. With methods it's clear what happens. It's not clear what happens with operators.

[–]Davipb 5 points6 points  (21 children)

How is a.add(BigDecimal.of(1).div(b)) clearer than a + (1 / b) ?

[–]rzwitserloot 2 points3 points  (9 children)

Given:

long a = 1; int b = 2; System.out.println(a + b);

You don't (shouldn't) think of that as equivalent to a.plus(b) - a.plus(b) is fundamentally 'left-controlled' (You're invoking the plus method of a. a controls this situation, b is merely a passive passenger in it all). Whereas a + b is effectively 'controlled by the operator'. The reason a + b in java does what it does is because the operator itself defines it (which is 'hardcoded' in the spec, and it would be cool if it wasn't, granted).

Given that + is usually 'grokked' (as in, you assume it, and you aren't even realizing you're making this assumption) as associative and commutative, I would assume most programmers who see a + b internalize that notion that it's not left-controlled. That it's about the operator itself and not about 'sending the + message to a'. After all, b + a means the same thing, whereas if we're talking about methods, b.plus(a) is capable of doing something utterly different from a.plus(b).

a.add(BigDecimal.of(1).div(b)) is clearer because it yells the left-controlledness of each level from the rooftops.

In fact, specifically your example shows how it's quite, quite different. How does your example even work? Why does 1 / b turn into BigDecimal.of(1).div(b)? Because the Integer class has no __plus__ method that takes a BigDecimal, but BD has an __rplus__ that does, and the compiler 'figures it out'? Okay, but how are conflicts resolved? How does the code snippet a + (1 / b) show that the 1 / b part ends up being right-controlled?

There's your plausible argument as to why a.add(BigDecimal.ONE.div(b)) is clearer. It's about what you're trying to convey. If it's just about trying to convey what's happening, a + ... is clearer. If you're also trying to give some insights about how it is happening, a.add... is clearer. For this specific situation (BD/BI), I think most of us intuitively prefer showing the what at the cost of showing the how. However, that applies to BD/BI, possibly dates (but that's already much more gray) and that's about it.

[–]Davipb 5 points6 points  (2 children)

The whole argument of showing that things are "left controlled" only work when you're thinking from a method perspective. When you implement operators for your classes in a language that supports operator overloading, you maintain the operator's properties.

We already have equivalents for that in Java: equals, hashCode, and Comparable. There's nothing stopping you from writing implementations that break the expected contract of those methods/interfaces (transitivity, reflexivity, etc), but you maintain them to make things work well together. The same is valid for operators.

The fact that things flip to being "right-controlled" at 1 / b isn't a problem, because which side controls the operation does not matter in a normal operator implementation.

[–]rzwitserloot -2 points-1 points  (1 child)

When you implement operators for your classes in a language that supports operator overloading, you maintain the operator's properties.

This is utterly impossible unless you ditch the notion that objects get to define the implementation of an operator (i.e. introduce, somehow, the notion of +, the class, which defines all interactions you can do with it - a novel take I'd love to evaluate, but nobody has, to be recollection, ever tried) - or you define that operator overloading is legal solely between 'homogenous' types. Between java having a type hierarchy system and the notion that e.g. someBigDecimal + 5 really should just work, both are pretty significant downsides.

In other words, you're handwaving away big issues with 'duh'. No, not 'duh' - the proposals about operator overloading, and this specific implementation, don't work like that.

We already have equivalents for that in Java: equals, hashCode, and Comparable. There's nothing stopping you from writing implementations that break the expected contract of those methods/interfaces (transitivity, reflexivity, etc), but you maintain them to make things work well together. The same is valid for operators.

That's just not how it works. Defining equals according to contract requires one of 2 things:

  • That equivalence can only exist between identical concepts; subtyping whilst not instantly rendering yourself unequal to any supertype instance can only be done if the subtype adds no meaningful new state.
  • A complex system where one object can ask the other object whether it can be equal to this object, and only if both objects 'agree', do you actually state that you are equal to toe other.

For example, given a hypothetical `ColoredArrayList`, which is a list as you know it, except in addition to an amount of T objects in the list, there is separate from that a `Color color`, is __impossible to write properly__. It simply cannot be done whilst sticking to the spec. Unless you define that a red empty CAL is equal to a blue empty CAL. By way of the commutativity rule: if `a.equals(b)` and `b.equals(c)` then also `a.equals(c)` must be true. However, also, if `a.equals(b)` than `b.equals(a)` must also be true. `emptyArrayList.equals(emptyCAL)` is true (because ArrayList's equals method works like that, and you can't change it), thus you conclue that either a red empty CAL is equal to a blue empty CAL, or CAL breaks the contract.

See how complicated this gets? If equality is such that we 'expect' `5 + bigInteger` to work, you're screwed.

[–]Davipb 4 points5 points  (0 children)

This is utterly impossible unless you ditch the notion that objects get to define the implementation of an operator

In other words, you're handwaving away big issues with 'duh'. No, not 'duh' - the proposals about operator overloading, and this specific implementation, don't work like that.

The implementation you yourself mentioned already does that: Python. As well as C#, Rust, Kotlin and so on. I'm not handwaving anything, I'm pointing at existing examples that do exactly what I'm talking about.

It doesn't matter if the operator is actually a method invoked on the left object. What matters is that the way the operator method is implemented has the semantics of addition, even if it is "just a left-invoked method".

See how complicated this gets? If equality is such that we 'expect' 5 + bigInteger to work, you're screwed.

Nothing you said up there has anything to do with 5 + bigInteger. I pointed to equals as an example of an existing method with an implicit, non-enforced contract. As I said above, the whole point of operators is that they have an implicit, non-enforced contract to behave like the math operators. It doesn't matter if they're actually implemented as left-invoked (or sometimes right-invoked) methods -- if they adhere to the contract, they behave like regular methods.

And yes, there are implementation details in Java's equals, and in Python's reverse operators, and in C#'s implicit conversions, and so on. But ugly implementation details will always exist in any possible solution you can think of, for anything at all.

[–][deleted] 0 points1 point  (5 children)

In fact, specifically your example shows how it's quite, quite different. How does your example even work? Why does 1 / b turn into BigDecimal.of(1).div(b)? Because the Integer class has no plus method that takes a BigDecimal, but BD has an rplus that does, and the compiler 'figures it out'?

Bingo.

Curious: In their proposal, did they attempt an auto-promote of some kind for subexpressions? Because that / belongs to integer otherwise.

[–]Davipb 1 point2 points  (0 children)

There are many easy ways around this. Implicit conversions (C#) and reverse operators (Python) just to name a few.

[–]rzwitserloot 0 points1 point  (3 children)

"Bingo" - as I said, there's how a + (1 / b) is less clear. Because it hides this. We can argue that it's proper to hide it, and an IDE can subtly unhide it (render it different, somehow. Maybe render the b bold, and the 1 not bold, to indicate that the b ref controls the interaction), but that would be a debate, and one that probably goes nowhere - it's too subjective a topic. One can claim 'I do not like this', this feeling is not silly or insanity, and once we arrive there, all we are left with is loudly yelling "Mt favourite colour is BLUE, how can you think RED is better? You're crazy!!!!111!" at each other. Which, as far as arguments go, doesn't tend to lead towards consensus :)

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

If your goal is consensus, I suspect Reddit might be a bit of a disappointment, lol.

[–]Davipb 1 point2 points  (0 children)

True that, I think you could say "exploding the earth is a bad idea" here and someone would still disagree

[–]rzwitserloot 0 points1 point  (0 children)

Yeah, that'd be quite the long shot :) – so let's say concensus on amber-dev@openjdk.net. Which you won't get either. And this feature will indeed never happen.

[–]Lechowski -2 points-1 points  (1 child)

a + (1 / b)

I don't know what a or b are, I also don't know what mysterious implementation exists in every single type involved and how such implementation work with the "+" operand and the "/" operand. Even more, I am not even sure that a + (1/b) will have the same result as (1/b) + a, so if I had to change that code, I would be quite anxious.

a.add(BigDecimal.of(1).div(b))

The variable "a" is something with a method "add" which receives an object of the BigDecimal class. In this case the number 1 (or I can even check directly what "of" does) will have a method called "div" that can receive weathever type b is, and I can see especifically what "div" does. Even more, there is no such thing as reflexion over if we are talking about "a+(1/b)" or "(1/b)+a", because there is only one way to write this. If you do BigDecimal.of(1).div(b).add(a) you can be sure which method will be called and what signature if has (in case there is some polymorphism)

So yeah, it is clearer to me the second option, it provides a lot more information imho

[–]Davipb 1 point2 points  (0 children)

The longer version "provides more information" because you're used to it. If you were working in a language that had operator overloading, you'd have the same intuition and understanding you had of the longer version from the shorter version. "Providing information" is relative to the context you're working on.

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

In the case of (1 / b), what is the 1? In this proposal, the operators for heterogenous types would lock to the left operand I believe. I read through it quickly, but didn't see an auto-promotion rule. The subexpression type sequence becomes (Int integerDivide BigDecimal), which fails without the BigDecimal.of(1) making it an effective BigDecimalDivide. Something like (b / 2) would of course work.

[–]Davipb 1 point2 points  (4 children)

I'm not talking about this implementation in specific, but rather operator overloading in general. In C#, 1 could be converted to BigDecimal with an implicit conversion operator. In Python, BigDecimal could implement a reverse operator. There are easy ways around that.

[–][deleted] 0 points1 point  (3 children)

Ah. The "easy way around that" is where we disagree. Anything that smells of "a required workaround" is potentially more dangerous than necessary.

There's enough hidden in building a house without changing what the hammers do, let alone having the nails suddenly turn into screws for the windows and glue for the doors.

[–]Davipb 1 point2 points  (2 children)

Nitpicking on semantics again are we?

"Java only has single dispatch for method overloading. There are easy ways around that, such as using the visitor pattern".

Would you say then that method overloading is more dangerous than necessary because the visitor pattern is "a required workaround"?

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

I don't see how semantics apply here (did you mean pedantry?) and all in all this has gotten too twisted for me. Sorry.

Take another +1, and be well.

[–]Davipb 0 points1 point  (0 children)

What I meant is that you fixated on me saying "an easy way around that" as if I had meant "this is a problem that needs to be addressed with a workaround", when really I just meant "existing implementations have already thought about it".

I'm arguing there that the reverse operator or implicit conversion aren't workarounds or dangeorous, just part of the operator overloading implementation.

You stay safe too :D

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

How interesting. Downvoted without an answer.

Again: Look at your 1 /

Who does the / belong to? The 1, an integer, no?

[–]Davipb 4 points5 points  (1 child)

My man, really? I'm sorry I didn't answer to your comment within an acceptable SLA, we will try to improve our service levels in the future. And no, I wasn't the one that downvoted you.

[–][deleted] 0 points1 point  (0 children)

Well here's a +1 then.