Operator overloading [was Re: 7.0 wishlist?]



hzergel901@xxxxxxxxx wrote:
Immutable Rectangles don't have the problem with Square, as was
pointed out elsewhere in this thread (which, since my name came up in
it, I have since had the time to read more-or-less thoroughly).

A reason why immutability is a good thing. In any case, the intent I attempted to convey was the fallacy of naïve translation of mathematical concepts to programming.

* They might internally cache information; for example, a Matrix its
determinant, computing it lazily when it's first requested. Then if
m.getDeterminant() appears multiple times in an expression,
technically this has a side effect, but semantically it doesn't; the
point is that the semantics of the determinant are that it is an
immutable property of the immutable Matrix and so it does not matter
which of the calls to m.getDeterminant() in a particular expression
(if any; it might already have been computed earlier) actually
computes it; all of the calls return the same scalar and the Matrix's
identity is not mutated, unlike an int being affected by expressions
containing "i++".

A side effect is a side effect. True, some may be only visible to the internal code in the API, but, as far as the JLS is concerned, it's a side effect if some Java code could detect the difference.

(The above assumes correct code).

While users ideally need only deal with correct code, the specification must deal with any valid code. To paraphrase a CSS spec developer: "You may not worry about what a web page looks like on a 3x3-pixel screen, but a browser has to worry about that."

Even given mutability, though, I don't see the problem with the
operator overloading proposal at issue, which resembles one of my own
suggestions of a year or so ago. As long as the translation of
operators into method calls is well-specified, with the future JLS
version clearly explicating what pre-overloading code has the same
semantics, then programmers can figure out when, in what order, and
how many times any side effects will occur. Just as they presently can
when an expression has multiple occurrences of "i++" in it.

The JLS could say whatever it wants, since it is the specification. In reality it's bounded by common sense. The changes have to be approved by a group of people; I doubt a change that would break either of these conditions would fly:

* User-overloaded operators would evaluate in the same order as the operators they are based on, i.e. operands are evaluated as they appear and associate as the operators (left-to-right except for the few right-to-left ones).
* An expression involving user-overloaded operators can be rewritten as an expression (not a series of statements) using the base methods.

If anything, a problem with Harry's proposal is that .plus could be
implemented to be noncommutative. And any of these could be made non-
associative, too. But the possibility that someone will write bad code
is not reasonably avoidable (and sometimes there's a use for non-
associative operations, though heaven forfend that anyone call THOSE +
instead of *).

For the sake of simplicity, I think we can agree on the following:
1. Some user-defined overloaded operators may be noncommutative.
2. The spirit of operator overloading should be agnostic to the name of the operator being overloaded, modulo something like interface names or the arity of the operator. So if we assume that one binary operator could be noncommutative, we should assume that all binary operators could be noncommutative, even if it would be bad practice to do so.

Interfaces might also be used; for example, + might require at least
one operand implement Addable<OtherOperandsType>, which specifies
plus, and not just that it have the plus method. That would have
problems with the present inability to implement both Addable<X> and
Addable<Y> in the same class. Reified generics might fix that.

My preferred method for overloading would involve interfaces like that:

public interface Addable<T, R> {
R add(T);
}
// etc.

public class Matrix<T extends Number> implements
Addable<Matrix<T>, Matrix<T>>,
Multipliable<Matrix<T>, Matrix<T>>,
Multipliable<Vector<T>, Vector<T>>, // NOT java.util.Vector
Multipliable<T, Matrix<T>> {
}

Actually, probably even better is this:
public class Matrix<T extends
Addable<? super T, ? extends T> &
Multipliable<? super T, ? extends T>>
implements // etc.

A proposal I saw on the possibility of "contracts" (or static implementation of interfaces) gave me an idea to work around the LHS-issue with a bit more workaround:

public interface Addable<Left, Right, Value> {
Value add(Left left, Right right);
}

public class Matrix<T extends ...> implements
static Addable<Matrix<T>, Matrix<T>, Matrix<T>>,
static Multipliable<T, Matrix<T>, Matrix<T>> {

static Matrix<T> multiply(T scalar, Matrix<T> matrix) {
Matrix<T> result = new Matrix<T>(matrix);
for (T[] row : result.rows) {
for (int i = 0; i < row.length; i++) {
row[i] = T * row[i];
}
}
}
}

with suitable `? super' and `? extends' thrown in the type definitions. Writing generics libraries is still a pain, though.

Thus the compiler could then translate the expression |scalar * matrix| to |multiply(scalar, matrix)| and then select the correct method to call for multiply based on normal method resolution rules. The only catch (which my example shows) is that the correct way to write the libraries is still difficult.

Well, there's also the fact that it depends on not one but two proposals before it.

Given such a fix, Addable and similar interfaces enable extending
java.util (or java.math?) with useful new things, for example an
accumulate method that can take a Collection<Addable<Foo>> and return
a Foo that is their sum, or an Accumulator<Foo> that can perform
accumulations.

Implementation via interfaces opens up wide possibilities that could not be offered by other proposals for operator overloading. Indeed, it is probably safe to say that these interfaces alone--along with retrofitting the appropriate primitive wrappers, BigInteger, and BigDecimal--would be powerful enough, even if operator overloading didn't follow.

> Or rather, the compiler can turn "foo" +
xString + "bar" + "baz" + yString into an accumulation, and can even
turn other + chains (if it assumes + is commutative, associative, and
non-mutating, which assumption would be documented) into accumulations
that might be more efficient, effectively generalizing the present
special-casing of chains of String +s to all Addables.

One step at a time, I'd say. Pin down the implementation of regular operator overloading before trying to extend it.
Addable's
contract would specify that .plus be commutative, associative, and non-
mutating -- no worse than the existing equals and hashCode contract,
comparable and equals contract, and similar contracts the compiler
can't enforce, really.

Hmm...

public interface CumulativeAddable<Param, Return> extends
Addable<Param, Param, Return> {
public Return add(Param... params);
}

Sometimes I wish there were a way to say in an interface that "this method is actually equivalent to this one."

> Accumulator would need three type parameters to
fully generalize this -- left-summand type, right-summand type, and
result type; StringBuilder would implement
Accumulator<String,Object,String> since it can add any Object to a
String and return a String, for instance.

But it can also add any String to an Object and get a String. Unfortunately.

--
Beware of bugs in the above code; I have only proved it correct, not tried it. -- Donald E. Knuth
.



Relevant Pages

  • Re: iterating the difference of two collections
    ... part of its interface. ... the fact that a class extends another class would become "protected". ... public class B extends AbstractA ... ... and to check baz ...
    (comp.lang.java.programmer)
  • Re: Visitor pattern vs if-ladder
    ... The normal method is to use a marker interface for different implementations with the same type: ... class Car1 extends Vehicle implements Car ... interface Car extends VehicleMarker{} ...
    (comp.lang.java.programmer)
  • Re: [PHP] interface inheritance
    ... interface inheritance, lamenting that php is void of the feature. ... public function doSomething; ... interface Extendable extends Somethingable, Crazyfiable { ...
    (php.general)
  • Re: Wishlist for Delphi 10 / 2006
    ... The value of operator overloading is purely in the abstraction of value ... would like to offer the same interface. ... Is it A Good Thing for the language to offer this kind of abstraction? ... This becomes more significant as you start writing generic code, ...
    (borland.public.delphi.non-technical)
  • Re: ADTs vs. interfaces
    ... >>mixture of abstract methods and concrete methods and your variables ... >>With an interface, it's a little different. ... you can't use extends from an interface. ... > public class BigBooks implements Books { ...
    (comp.lang.java.help)