julia> struct abc
x :: Int
end
julia> Base.:(+)(a::abc, b::Int) = abc(a.x + b)
julia> abc(1) + 1
abc(2)
julia> 1 + abc(1)
ERROR: MethodError: no method matching +(::Int64, ::abc)
julia> Base.:(==)(a::abc, b::Int) = a.x == b
julia> abc(1) == 1
true
julia> 1 == abc(1)
false
Shouldn’t the second method in each case automatically be defined in terms of the first? This breaks commutativity in case of the equality. A missing method would have been better than this instead of a fallback.
The thing is that binary operators are just functions like all other functions, they are in no way special-cased in the language. Thus, they obey the same rules as all other functions. You could define == to be non commutative for your own type if you wanted to, because it’s just a regular function.
I wonder if this was considered. There would have to be some way of marking some functions (or some symbols) as being commutative… and perhaps the extra complication was not thought worthwhile?
It’d be great if there is a way to encode some algebraic properties of binary operators like associativity and commutativity (and approximation of them)! IIUC Fortress uses it to let the compiler automatically parallelize reduction. A relevant RFC PR:
So I actually have an algebra pkg with non-commutative + operation, so + is not always commutative.
No, it would not be worthwhile. Methods should not have properties like this because multiple-dispatch would actually handle this type of issue in Julia already.
Instead of encoding algebraic properties on some functions, it might make sense to make a macro (e.g. @commute) which would define multiple methods in one line. For binary operators it’s doesn’t save very much space (i.e. one line vs two), but if there were methods with three or more inputs which all can be commuted, such a macro would cut the number of method definitions down a lot. Thoughts?
I think this would give you more control over what methods to make commutative or not. For example, you may want different methods for a custom type with a real number like abc(1) + 1 and 1 + abc(1) and other cases where you would want to commute the inputs. In particular, there are probably a lot of functions where a custom type with a missing should return a missing regardless of the order.
@commute Base.:(+)(a::abc, b::Missing) = missing
Encoding algebraic properties into functions could be beneficial if there are some performance optimizations that could occur from some algebraic manipulation. That seems like a lot of complexity to add to highly generalized functions. That being said, defining commuted methods with a macro could be a good first step to implementing such optimizations. Commutativity seems like an obvious property to add as a macro – I’m not as sure of other algebraic properties.
On the syntax level, the only multi-argument infix operators are +, *, and the ever-mysterious ++. All other operators just take two arguments and do not chain in infix.
Also, multi-argument forms are only needed for non-associative operators, or if there is some efficiency advantage in doing things in one pass. Otherwise folding works fine by falling back to the 2-argument case.