PROPOSAL: Simple Operator Overloading

rssh at gradsoft.com.ua rssh at gradsoft.com.ua
Sun Mar 29 06:54:27 PDT 2009


Permanent URL:
 http://docs.google.com/Doc?id=dhhvggr8_18djp85shk

Proof of concept implementation:
 http://code.google.com/p/jsoo-coin/

Submission as text:


OVERVIEW:
 FEATURE SUMMARY:

   Add possibility to overloaded common subset of operators for java classes.
Submission define @Operator method annotation for methods, which can be
called as operators in classes and interfaces.


 MAJOR ADVANTAGE:

   In many cases (especially for Collections and own subtypes of numeric)
using
 operator syntax is more clear and simple, than call by name of method.

 This proposal allows use operator ovlerloading, and at same time avoid
complexity
  by defining operator overloading as thin layer on top of usual method
calls.

 MAJOR DISADVANTAGE

   Inaccurate usage of this feature can cause producing of code which hard
 to read.
   Compiler is slightly complex then before.

 ALTERNATIVES:

   Live without operators as before.

EXAMPLES

SIMPLE EXAMPLE:

Example 1
import javax.lang.Operator;

public class Z3
{
  public Z3(int value)
    { this.value = (value%3); }

  public Z3(Z3 z3)
    { this.value = z3.value; }

  @Operator("+")
  public  Z3 plus(Z3 x)
    {  return new Z3(value + x.value); }

  @Operator("+=")
  public  Z3 plusAssign(Z3 x)
    {  value=((value+x.value)%3); }

  @Operator("-")
  public Z3 minus(Z3 x)
    {  return new Z3(value-x.value); }

  @Operator("-=")
  public  Z3 minusAssign(Z3 x)
    {  value=((value-x.value)%3); }

  private int value;
}

Usage:

 Z3 z3 = new Z3(2);
 Z3 z4 = z3+z3;

Example 2

public interface Named
{
  public String getName();
}

public class IndexedCollection<T extends Named>
{

  IndexedCollection(int capacity)
  { byIndexes=new T[capacity];
    byNames=new TreeMap<String,T>();
  }

  @Operator("[]")
  public T get(int i)
  {  return byIndexes[i]; }

  @Operator("[]")
  public T get(String name)
  {  return byNames.get(name);  }

  @Operator("[]=")
  public void set(int i, T t)
  {
     byIndexes[i]=t;
     byNames.put(t.getName(),t);
  }

  private T[] byIndexes;
  private Map<String,T> byNames;
}


Usage:

class NVPair extends Named
{
  public NVPair(String name, Object value)
  { this.name = name;
    this.value = value;
  }

  public String getName()
  { return name; }

  public String getValue()
  { return value; }

  private String name;
  private String value;
}

IndexedCollection<NVPair>  pairs = new IndexedCollection<NVPair>(10);
pairs[0]=new NVPair("CISCO-AV-Pair","lcp:interface=1");
pairs[1]=new NVPair("X",4);
pairs[2]=new NVPair("Y",5);

NVPair pair1=pairs[1];
NVPair pair2=pairs["X"];

ADVANCED EXAMPLE:


public interface  StringAppendable
{
    @Operator("+=")
    public  void append(String x);
}


public class X implements StringAppendable
{
     ....
    public void append(String x)
    { value+=x; }
}

X x;
x+="qqq";

public interface  X
{
  @Operator("+=");
   public void  addZ(Z z)
}

public interface  Y
{
  @Operator("+=");
   public void  plusZ(Z z)
}

public class XY implements X, Y
{
   public void addZ(Z z);
   public void plusZ(Z z);
}

XY xy;
xy+=z; //  compile-time error. (ambiguity of operator call resolution)


DETAILS:

 Add to Java Library @Operator method annotation with next signature:

package java.lang.Annotation

@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface Operator
{
   public String value();
}

JLS changed.

Add  9.6.1.7 about Operator annotation:

The annotation type annotation.Operator(v) is used to indicate, that
annotated
method can be shortcated as operator in program test.

v is String, which can be one of:
(
 "[]", "[]=", "+", "-", "~", "!", "*", "/", "%", "+=", "-=", "*=", "/=", %=
)

Signature of operator method must be compatible with signature of operator,
 which means that methods, annotated as binary operators must be nonstatic
 member functions with one argument; unary operators - as nonstatic member
 functions without arguments.


Add to 15.12.1.   Compile-Time Step 1: Determine Class or Interface to Search
Case for search of method for operator expression:

    If the form is OperatorExpression then classe for search is first
operand of expression.
Change 15.12.2.1 Identify Potentially Applicable Methods  to
A member method is potentially applicable to a method invocation if and
only if all of the following are true:

    * Names of operator or method are matched, i.e.
          o When analyzed invocation is  method invocation by name:
                +  the name of the member is identical to the name of the
method in the method invocation.
          o   if  method invocation is call of overloaded operator,
                + method is annotated with @Operator annotations with 
operator name as in operator expression or
                + method is override or implements  method in superclass
or superinterfaces, annotated with @Operator annotation
with the name same as in operator expression.
    * The member is accessible (&#167;6.6) to the class or interface in
which the method invocation appears.
    * The arity of the member is lesser or equal to the arity of the
method invocation.
    * If the member is a variable arity method with arity n, the arity of
the method invocation is greater or equal to n-1.
    * If the member is a fixed arity method with arity n, the arity of the
method invocation is equal to n.
    * If the method invocation includes explicit type parameters, and the
member is a generic method, then the number of actual type parameters
is equal to the number of formal type parameters.



To chapter 15.13 to next form:

 The type of the array reference expression must be an array type (call it
T[], an array whose components are of type T) or type of class with
methods, annotated by @Operator("[]") or @Operator("[]=")
annotations(call one class array reference expression) or a compile-time
error occurs.

Chapter 15.13.1 (Runtime Evaluation of Array Access) to next form:

An array access expression is evaluated using the following procedure:


    * First, the array reference expression is evaluated. If this
evaluation completes abruptly, then the array access completes
abruptly for the same reason and the index expression is not
evaluated.
    * Otherwise,
          o if array reference is array
                +  the index expression is evaluated. If this evaluation
completes abruptly, then the array access completes
abruptly for the same reason.
                + Otherwise, if the value of the array reference
expression is null, then a NullPointerException is thrown.
                + Otherwise, the value of the array reference expression
indeed refers to an array. If the value of the index
expression is less than zero, or greater than or equal to
the array's length, then an ArrayIndexOutOfBoundsException
is thrown.
                + Otherwise, the result of the array access is the
variable of type T, within the array, selected by the
value of the index expression. (Note that this resulting
variable, which is a component of the array, is never
considered final, even if the array reference expression
is a final variable.
          o if array reference is class-array-reference exception
                + if exists method, annotated as @Operator("[]=") and if
such expression is situated at the left part of plain
assigment expression X=Y, than this subexpression does not
evaluated itself, instead components of array access
expression take  part in process of assigment.
                + otherwise, if exists set of methods for such class,
annotated by @Operator("[]"), than better candidate from
set of appropriative annotated methods is called, as
specified in 15.12 (Method Invocation Expression).


15.15.5 Bitwise Complement Operator ~
The type of the operand expression of the unary ~ operator must be a type
that is convertible (&#167;5.1.8) to a primitive integral type, or class,
which declare or override or implement non-static member functions without
arguments, annotated by @Operator("~") , or a compile-time error occurs.
  If operand expression is convertable to a primitive integram type, than

    *  Unary numeric promotion (&#167;) is performed on the operand. The
type of the unary bitwise complement expression is the promoted type
of the operand.

    * At run time, the value of the unary bitwise complement expression is
the bitwise complement of the promoted value of the operand; note
that, in all cases, ~x equals (-x)-1

  If operand expression is class, where methods annotated by
@Operator("~") are accessible, than appropriative methods is called, as
specified in 15.12 ( except check for name of method step)
and result type is type of method invocaton.


15.15.6 Logical Complement Operator !
The type of the operand expression of the unary ! operator must be boolean
or Boolean, or class, which declare, override or implements function
without arguments, annotated by @Operator("!") , or a compile-time error
occurs.
When type of the operand expression is boolean or Boolean, than

    * type unary logical complement expression is boolean,
    * At run time, the operand is subject to unboxing conversion
(&#167;5.1.8) if necessary; the value of the unary logical complement
expression is true if the (possibly converted) operand value is false
and false if the (possibly converted) operand value is true

When type of the operand expression is class, which have non-static member
functions without arguments, annotated by @Operator("!") than expression
is processed as invocation  of appropriative method.

15.17 Multiplicative Operators
The operators *, /, and % are called the multiplicative operators. They
have the same precedence and are syntactically left-associative (they
group left-to-right).


    MultiplicativeExpression:
     UnaryExpression
     MultiplicativeExpression * UnaryExpression
     MultiplicativeExpression / UnaryExpression
     MultiplicativeExpression % UnaryExpression

The type of each of the operands of a multiplicative operator must be a
type that is convertible (&#167;5.1.8) to a primitive numeric type, or 
type of first operand must be a class, which contains (or implements or
override)  accessible methods with one argument, annotated by
@Operator("*" or "/" or "%"), or compile-time error occurs.

In case of types, convertible to promitive numeric type: Binary numeric
promotion is performed on the operands (&#167;5.6.2). The type of a
multiplicative expression is the promoted type of its operands. If this
promoted type is int or long, then integer arithmetic is performed; if
this promoted type is float or double, then floating-point arithmetic is
performed.

Note that binary numeric promotion performs unboxing conversion
(&#167;5.1.8) and value set conversion (&#167;5.1.13).


Chapter 15.17.1, 15.17.2, 15.17.3 just rename to Primitive Numeric
Multiplication Operator, Primitive Numeric Division Operator and Primitive
Numeric Remind operator.


Add 15.17.4  Multiplicative Operators for class with operator-annotated
methods.


In case when type of first operand is class with method, annotated by 
appropreative annotations, than expression is processed as invocation
expression ($15.2) from set of appropriative  annotated methods.


15.18  Additive Operators

The operators + and - are called the additive operators. They have the
same precedence and are syntactically left-associative (they group
left-to-right).

    AdditiveExpression:
     MultiplicativeExpression
     AdditiveExpression + MultiplicativeExpression
     AdditiveExpression - MultiplicativeExpression

If the type of either operand of a + operator is String, then the
operation is string concatenation.

Otherwise, the type of each of the operands of the + operator must be a
type that is convertible (&#167;5.1.8) to a primitive numeric type. In
every case, the type of each of the operands of the binary - operator must
be a type that is convertible (&#167;5.1.8) to a primitive numeric type,
or a compile-time error occurs.

Otheriwise, the type of first operand must be a class, which contains or
override or implement nonstatic member function with one argument,
annotated by @Operator("+" or "-")  or compile-time error occurs.



add 15.18.3  Additive Operators for class with operator-annotated methods:

In case when type of first operand is class with method, annotated by 
appropreative annotations, than expression is processed as invocation
expression from set of appropriative  annotated methods.


15.26 There are 12 assignment operators; all are syntactically
right-associative (they group right-to-left). Thus, a=b=c means a=(b=c),
which assigns the value of c to b and then assigns the value of b to a.


    AssignmentExpression:
     ConditionalExpression
     Assignment

    Assignment:
     LeftHandSide AssignmentOperator AssignmentExpression

    LeftHandSide:
     ExpressionName
     FieldAccess
     ArrayAccess

    AssignmentOperator: one of
     = *= /= %= += -= <<= >>= >>>= &= ^= |=

The result of the first operand of an assignment operator must be a
variable, or class array reference expression ($15.3), or a compile-time
error occurs. Variable operand may be a named variable, such as a local
variable or a field of the current object or class, or it may be a
computed variable, as can result from a field access (&#167;15.11) or an
array access (&#167;15.13). The type of the assignment expression is the
type of the variable after capture conversion (&#167;5.1.10).


15.26.1 Simple Assignment Operator =
A compile-time error occurs when

    *    left hand operand expression is a value and the type of the
right-hand operand cannot be converted to the type of the variable by
assignment conversion (&#167;5.2)
    *    left hand operand expression is class array reference expression
and type of right-hand operator cannot be converted to the type of
second argument for one of methods, annotated with @Operator("[]=") 
annotation.


At run time, the expression is evaluated in one of three ways:

    * If the left-hand operand expression is a field access expression
(&#167;15.11) e.f, possibly enclosed in one or more pairs of
parentheses, then:
          o First, the expression e is evaluated. If evaluation of e
completes abruptly, the assignment expression completes abruptly
for the same reason.
          o Next, the right hand operand is evaluated. If evaluation of
the right hand expression completes abruptly, the assignment
expression completes abruptly for the same reason.
          o Then, if the field denoted by e.f is not static and the result
of the evaluation of e above is null, then a
NullPointerException is thrown.
          o Otherwise, the variable denoted by e.f is assigned the value
of the right hand operand as computed above.
    * If the left-hand operand is an array access expression
(&#167;15.13), possibly enclosed in one or more pairs of parentheses,
then:
    *
          o If array reference expression is array type
                + First, the array reference subexpression of the
left-hand operand array access expression is evaluated. If
this evaluation completes abruptly, then the assignment
expression completes abruptly for the same reason; the
index subexpression (of the left-hand operand array access
expression) and the right-hand operand are not evaluated
and no assignment occurs.
                + Otherwise, the index subexpression of the left-hand
operand array access expression is evaluated. If this
evaluation completes abruptly, then the assignment
expression completes abruptly for the same reason and the
right-hand operand is not evaluated and no assignment
occurs.
                + Otherwise, the right-hand operand is evaluated. If this
evaluation completes abruptly, then the assignment
expression completes abruptly for the same reason and no
assignment occurs.
                + Otherwise, if the value of the array reference
subexpression is null,  no assignment occurs and a
NullPointerException is thrown.
                + Otherwise, the value of the array reference
subexpression indeed refers to an array. If the value of
the index subexpression is less than zero, or greater than
or equal to the length of the array, then no assignment
occurs and an ArrayIndexOutOfBoundsException is thrown.
                + Otherwise, the value of the index subexpression is used
to select a component of the array referred to by the
value of the array reference subexpression. This component
is a variable; call its type SC. Also, let TC be the type
of the left-hand operand of the assignment operator as
determined at compile time.
                + If TC is a primitive type, then SC is necessarily the
same as TC. The value of the right-hand operand is
converted to the type of the selected array component, is
subjected to value set conversion (&#167;5.1.13) to the
appropriate standard value set (not an extended-exponent
value set), and the result of the conversion is stored
into the array component.
                + If TC is a reference type, then SC may not be the same
as TC, but rather a type that extends or implements TC.
Let RC be the class of the object referred to by the value
of the right-hand operand at run time.

                  The compiler may be able to prove at compile time that
the array component will be of type TC exactly (for
example, TC might be final). But if the compiler cannot
prove at compile time that the array component will be
of type TC exactly, then a check must be performed at
run time to ensure that the class RC is assignment
compatible (&#167;5.2) with the actual type SC of the
array component. This check is similar to a narrowing
cast (&#167;5.5, &#167;15.16), except that if the check
fails, an ArrayStoreException is thrown rather than a
ClassCastException. Therefore:

                      # If class RC is not assignable to type SC, then no
assignment occurs and an ArrayStoreException is
thrown.
                + Otherwise, the reference value of the right-hand operand
is stored into the selected array component.
          o If array reference expression is class reference expression
($15.13), than
                + First, the first operand of array reference expression
is evaluated.  If this evaluation completes abruptly, then
the assignment expression completes abruptly for the same
reason; the index subexpression (of the left-hand operand
array access expression) and the right-hand operand are
not evaluated and no array assigmnent methods are called.
                + Otherwise, the index subexpression of the left-hand
operand array access expression is evaluated. If this
evaluation completes abruptly, then the assignment
expression completes abruptly for the same reason and the
right-hand operand is not evaluated and no array
assignment methods are called.
                + Otherwise, the right-hand operand is evaluated. If this
evaluation completes abruptly, then the assignment
expression completes abruptly for the same reason and no
call of array assignment method occurs.
                + Otherwise, invocation of appropriateve method, (which
annotated by @Operator("[]=") or override or implement
methods, annotated by Operator("[]=")) is called on first
operand of array reference with index subexpression and
righ hand operators as reference.
                + If appropriative method is not found, than compile-time
error occurs. (to prevent call single assigment on result
of method, annotated by Operator("[]")
    * Otherwise, three steps are required:
          o First, the left-hand operand is evaluated to produce a
variable. If this evaluation completes abruptly, then the
assignment expression completes abruptly for the same reason;
the right-hand operand is not evaluated and no assignment
occurs.
          o Otherwise, the right-hand operand is evaluated. If this
evaluation completes abruptly, then the assignment expression
completes abruptly for the same reason and no assignment occurs.


15.16.2 Compound Assignment Operators
A compound assignment expression of the form E1 op= E2  is
when

    *  type of E1 is Primitive Numeric Types or Booleans or String, then
this is equivalent to E1 = (T)((E1) op (E2)), where T is the type of
E1, except that E1 is evaluated only once.
    *  type of E1 is Class, which have methods m, annotated by
@Operator("op="), than compound assigment evaluates exactly as
invocation expression of appropriatev methods, with method names.


COMPILATION:

   During compilation overloaded operators will be translated to set of
appropriative annotated methods.
I.e. for

Example 1

 Z3 z3 = new Z3(2);
 Z3 z4 = z3+z3;

which will be translated to:

 Z3 z3 = new Z3(2);
 Z3 z4 = Z3.plus(z3,z3);

Example 2

 IndexedCollection<NVPair>  pairs = new IndexedCollection<NVPair>(10);
pairs[0]=new NVPair("CISCO-AV-Pair","lcp:interface=1");
pairs[1]=new NVPair("X",4);
pairs[2]=new NVPair("Y",5);

NVPair pair1=pairs[1];
NVPair pair2=pairs["X"];

will be equal to:

IndexedCollection<NVPair>  pairs = new IndexedCollection<NVPair>(10);

pairs.set(0,new NVPair("CISCO-AV-Pair","lcp:interface=1"));
pairs.set(1,new NVPair("X",4));
pairs.set(2,new NVPair("Y",5));

NVPair pair1=pairs.get(1);
NVPair pair2=pairs.get("X");


TESTING

  Implementation must  provide set of tests for each of operators and
possible confilcts for overriding/implementation and chained
invocations.

LIBRARY SUPPORT:

 @Operator  annotation must be added to standard library.

 Also, not required but be practical to  annotate standard Collection
classes;  BigDecimal and  BigInteger.

REFLECTIVE APIS: None


OTHER CHANGES: None


MIGRATION: None


COMPABILITY
None


REFERENCES

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4905919

IMPLEMENTATION PROTOTYPE URL

Proof of concept implementation, implemented as preprocessor, is published
at http://code.google.com/p/jsoo-coin/






More information about the coin-dev mailing list