I stumbled into a curious problem that seems like it surely must already been solved by someone before, maybe even somewhere in Base that I’m just not familar with. I figured I’d consult the brain-trust here to see if anyone had any good ideas.
Problem
Say I’ve defined some set of types A
, B
, C
, etc and I want to define a binary/two-argument function f
that acts on these and is commutative (i.e. f(x,y) == f(y,x)
). There are a large number of possible combinations of argument types and many but not all of them will be implemented. I’m looking for an elegant way to implement this commutativity.
Ideas
Here’s some thoughts I’ve had, but I’m very open to new ideas.
Option 1
For every pair of argument types that I intend to implement, I could explicitly define two methods.
f(x::A, y::B) = ...
f(x::B, y::A) = f(y, x)
f(x::A, y::C) = ...
f(x::C, y::A) = f(y, x)
...
Pro: It works. Not overly complicated.
Con: It requires a second boilerplate method for every implemented method. I have to remember to add both every time I implement a new method. For a large number of possible types, this is a non-trivial number of additional method definitions.
Maybe this could be implemented with a macro?
@commutative f(x::A, y::B) = ...
@commutative f(x::A, y::C) = ...
...
Option 2
I could define a single generic two-argument fallback method that reverses argument order.
f(x::A, y::B) = ...
f(x::A, y::C) = ...
f(x, y) = f(y, x)
Pro: Requires less boilerplate code.
Con: Generates a StackOverflow
if f
has not been implemented for this (x,y)
pair.
I considered adding a hasmethod(f, typeof.(y, x))
check or applicable(f, y, x)
in the fallback method with the hope that it would prevent recursion for unimplemented pairs, but this method definition itself would serve for (::Any, ::Any)
.