Open yole opened 8 years ago
I think, add
is a bad example not because its operands are not interchangeable, but because it's an operation mutating one of the operands, instead of returning the result.
For example consider an ImmutableCollection
with add
method defined as:
interface ImmutableCollection<E> : Collection<E> {
fun add(element: E): ImmutableCollection<E>
}
Then the function could be legitimately used as infix in the expression:
val values: ImmutableCollection<String> = ...
val newValues = values add "another"
@ilya-g In your example, the "add" method works exactly as the + operator, which is in effect a symmetric operation.
plus
is not symmetric for collection and element. If you don't like plus
, you can take minus
/ remove
as another example.
I don't think that and
is a good candidate. Infix functions should only be used when the operator(s) don't have any implied associativity which could cause confusion when chained together.
val result = w and x or y and z // always left-to-right
On a related note, I think this is a good argument for including bitwise operators in the language, but that's another topic. :)
I think, add is a bad example not because its operands are not interchangeable, but because it's an operation mutating one of the operands, instead of returning the result.
I support this 100%. All of the mathematics operators typically associated with C-like languages are referentially transparent (ie 'pure'). All mathematics operations, in addition to other symbolic operations like pointer de-referencing and array-index, do not modify the state of the program, instead placing any results or output in their return value.
This was combined with the re-aliasing functionality (read: var
functionality) of C and Java so that you could do things like variable += 4
, and that represents an interesting collusion of mutability and referential transparency, but at its core I think operators must be referentially transparent.
C#'s +=
for multicasting delegates was the first counter-example that I regularly used, and for me +=
got a special place for subscriber/event-registration. This was generalized to mutable maps and lists fairly quickly, such that operators containing an equal sign (eg +=
, -=
, /=
) may modify state, but regular operators (eg +
) such be pure, and their corresponding assignment counterpart (eg +=
) should only be overloaded with good cause.
I think it should be a firm (if not hard) rule that any infix operator is a pure function, only yielding a result value and not modifying either of its arguments.
Thus, a good operator might be
infix operator fun Path.div(another: Path) = this.resolve(another);
as resolve
is a pure function, and both the left and right side of this expression are not mutated.
But something like
infix operator List<Double>.div(scalar: Double) : Unit {
for(int i : this.indices){
this[i] = this[i] / scalar
}
}
would not, since it means the operator typically associated with the pure division function can now modify the contents of lists.
Declare a function as
infix
only when it works on two objects which play a similar role. Good examples:and
,to
,zip
. Bad example:add
.