Should + always be commutative?

From the manual:

While * may seem like a surprising choice to users of languages that provide + for string concatenation, this use of * has precedent in mathematics, particularly in abstract algebra.

In mathematics, + usually denotes a commutative operation, where the order of the operands does not matter. An example of this is matrix addition, where A + B == B + A for any matrices A and B that have the same shape. In contrast, * typically denotes a noncommutative operation, where the order of the operands does matter. An example of this is matrix multiplication, where in general A * B != B * A . As with matrix multiplication, string concatenation is noncommutative: greet * whom != whom * greet . As such, * is a more natural choice for an infix string concatenation operator, consistent with common mathematical use.

Sometimes I define my own operator methods. Should I use + only for commutative operations?

I think this will always be decided on a case by case basis. That said, it’s hard to think of anything non-commutative that + would be an appropriate operator for. Do you have anything specific in mind?

For example, I’m thinking of declarative plotting tools that combine plot layers with +, like ggplot2 (in R) and AlgebraOfGraphics.jl in Julia. In ggplot, the order of the arguments determines the layer order (i.e. which goes on top).

Yes, I would tend to recommend * or some other operator, rather than +, for this, in keeping with the style of Base.

8 Likes

That’s good to know. Are there other operator conventions that I can learn about?

(cc @piever in case you have thoughts on this)

In Gadfly, we use push!, discussion here

3 Likes

The one other thing I would consider is that in general, if you have both + and * defined, they probably should distribute with each other (ie a*(b+c)=a*b+a*c). Again, this isn’t a hard requirement, but if you don’t have a ring structure, you should at least think carefully before using + and *.

1 Like

Does floating-point arithmetic satisfy the distributive law? Intuitively I would think that not always.

No:

julia> 0.1 * (0.1 + 0.3)
0.04000000000000001

julia> (0.1 * 0.1) + (0.1 * 0.3)
0.04
1 Like

Yeah floating point sucks :frowning:. The good news is for floating point, I think you do actually get a good error bar where it will be equal up to 1 ULP.

1 Like

You heard it here first folks. +(::AbstractFloat, ::AbstractFloat) and *(::AbstractFloat, ::AbstractFloat) are now illegal.

8 Likes

In AlgebraOfGraphics, + and * are very close to forming a semiring structure (other than drawing order, I would say everything works, including distributivity), so I think the overloading is justified. One can always pass an explicit z-level to the layer to customize what is “drawn on top”.

The Base way to implement it would be something like vcat (to combine lists of layers) and kron (to do all pairwise products of layers, which is currently done via *), but I think that makes the code less readable. I had initially though about using ⊕, ⊗ (semiring up to isomorphism) but that was not considered particularly user-friendly or easy to type. I think that’s an important consideration for something like plotting, where you often just want to explore something quickly.

2 Likes