Thursday, January 26, 2012

Proposed changes for equality

Posted by Gilad Bracha


UPDATE: This post is no longer accurate. Please refer to the Dart language spec for the most up-to-date rules regarding equality.
This is a draft specification for the revised handling of equality. The essence of the change is as follows:
The == operator is no longer user definable. It has fixed behavior, that is however customizable via the operator/method equals(). Specifically, given x == y:
  • If x === y then return true. Otherwise
  • if either x or y is null, return false. Otherwise
  • return the result of x.equals(y)
The question arises, do we need the special story with operators for negate, call and equals? Can we just say that the operators are hardwired to invoke the methods involved?



Operators

Operators are instance methods with special names.
operatorSignature:
      returnType? operator operator formalParameterList
    ;

operator:
      unaryOperator
    | binaryOperator
    | '[' ']'
    | '[' ']' '='
    | negate
    | call
    | equals
    ;
    
unaryOperator:
      negateOperator
    ;

binaryOperator:
      multiplicativeOperator
    | additiveOperator
    | shiftOperator
    | relationalOperator
    | equalityOperator
    | bitwiseOperator
    ;

prefixOperator:
      '-'
    | negateOperator
    ;


negateOperator:
      '!'
    | '~'
    ;
An operator declaration is identified with built-in identifier operator.
The following names are allowed for user-defined operators: <, >, <=, >=, -, +, /, ~/, *, %, |, ^, &, <<, >>, []=, [], ~, callequalsnegate.
The built-in identifier negate is used to denote unary minus. The built-in identifier call is used to denote function application ( () ). The built-in identifier equals is used to denote equality (==).
Defining a nullary method named call, equals or negate will have the same effect as defining an operator, but is considered bad style, and will cause a static warning.
It is tempting to define it to be a compile-time error to declare a method named call, equals or negate. However, this causes compatibility problems. Since these are all built-in identifiers, unsanctioned use will cause a static warning, which is arguably sufficient to alert the programmer to the fact that the ported code is likely not intended to define an operator. In fresh Dart code, the warning will indicate that either the built-in identifier operator was forgotten, or that the method should have a different name.
It is a compile-time error if the number of formal parameters of the user-declared operator []= is not 2. It is a compile time error if the number of formal parameters of a user-declared operator with one of the names: equals, <, >, <=, >=, -, +, /, ~/, *, %, |, ^, &, <<, >>, [] is not 1. It is a compile time error if the arity of a user-declared operator with one of the names: ~, negate is not 0.
It is a compile-time error to declare an optional named parameter in an operator. The operator callcan have any arity.
It is a compile-time error to declare an optional named parameter in an operator, with the exception of the operator call.
It is a static warning if the return type of the user-declared operator []= is explicitly declared and notvoidIt is a static warning if the return type of the operator equals is explicitly declared and is not bool. It is a static warning if the return type of the operator negate is explicitly declared and not a numerical type.

Equality

Equality expressions test objects for identity or equality.
equalityExpression:
      relationalExpression (equalityOperator relationalExpression)?
    | super equalityOperator relationalExpression
    ;

equalityOperator:
      '=='
    | '!='
    | '==='
    | '!=='
    ;
An equality expression is either a relational expression, or an invocation of a equality operator on on either super or an expression e1, with argument e2.
Evaluation of an equality expression ee of the form e1 == e2 proceeds as follows:
  • The expression e1 is evaluated to an object o1.
  • The expression e2 is evaluated to an object o2.
  • If o1 === o2 evaluates to true, then ee evaluates to true. Otherwise,
  • If either o1 or o2 is null, then ee evaluates to false. Otherwise,
  • ee is equivalent to the method invocation o1.equals(o2).
Evaluation of an equality expression ee of the form super == e proceeds as follows:
  • The expression e is evaluated to an object o.
  • If this === o evaluates to true, then ee evaluates to true. Otherwise,
  • If either this or o is null, then ee evaluates to false. Otherwise,
  • ee is equivalent to the method invocation super.equals(o).
As a result of the above definition, user defined equals() methods can assume that their argument is not this and is non-null, and avoid the standard boiler-plate prelude:
    if (this === arg) return true;
    if (null === arg) return false;
  
Another implication is that there is never a need to use === to test against null, nor should anyone ever worry about whether to write null == e or e = = null.
An equality expression of the form e1 != e2 is equivalent to the expression !(e1 == e2 ). An equality expression of the form super != e is equivalent to the expression !(super == e)).
Evaluation of an equality expression ee of the form e1 === e2 proceeds as follows:
The expression e1 is evaluated to an object o1; then the expression e2 is evaluated to an object o2. Next, if o1 and o2 are the same object, then ee evaluates to true, otherwise ee evaluates to false.
An equality expression of the form super === e is equivalent to the expression this === e.
An equality expression of the form e1 !== e2 is equivalent to the expression !(e1 === e2 ). An equality expression of the form super !== e is equivalent to the expression !(super === e).
The static type of an equality expression of the form e1 === e2 is bool.
The static types of other equality expressions follow from the definitions above. The forms e1 != e2, e1 !== e2 , super != e and super !== e are negations and have static type bool. The expression e1 == e2 is typed as a method invocation so its static type depends on the operator method declaration. It had better be bool.