Recursive fallback methods error message

I have an interface which exposes some functions, let’s call them f and g here for the MWE.

The user is expected to define methods for user-defined types. But it is sufficient to define one of them, as the other can be derived. For some types, defining one is more natural than the other, but it varies by type, so there is no default ordering.

I would like to provide an informative error message instead of a StackOverflowError when the user fails to define either of the methods.

MWE:

Package defines

f(A, x) = g(A, x) + 1
g(A, x) = f(A, x) - 1

User defines

struct MyType end
f(::MyType, x) = abs2(x)

and everything works out:

julia> f(MyType(), 1)
1

julia> g(MyType(), 1)
0

User forgets to define methods

julia> struct MyOtherType end

julia> f(MyOtherType(), 1)
ERROR: StackOverflowError:
Stacktrace:
 [1] g(::MyOtherType, ::Int64) at ./REPL[88]:1
 [2] f(::MyOtherType, ::Int64) at ./REPL[87]:1
 ... (the last 2 lines are repeated 39998 more times)
 [79999] g(::MyOtherType, ::Int64) at ./REPL[88]:1

I would like to detect this somehow and provide suggestions.

Here’s a suggestion using a trait that requires the user to make one more definition:

function defbase end

f(A, x) = f(A,x,defbase(A))
g(A, x) = g(A,x,defbase(A))

f(A, x, ::typeof(g)) = g(A, x) + 1
g(A, x, ::typeof(f)) = f(A, x) - 1

and the user defines, e.g.,

struct MyType end
f(::MyType, x) = abs2(x)
defbase(::MyType) = f

If the user forgets either the f method or defbase (which I guess could have some better name for your particular use) they get a MethodError rather than StackOverflow.

This requires quadratically many method definitions in your package (n*(n-1) for n functions), but I’m not sure that can be avoided anyway.