How to change a public interface

Let’s say I have a package that defines an abstract type with an interface: if you define successor_is_prime, then my module will define predecessor_is_prime.

module Arithmetic
    function successor_is_prime end
    predecessor_is_prime(x::MyAbstractNumber) = successor_is_prime(x-2)
end

Now, I realize that I would rather have a new function is_prime, and define all the other functions in terms of it so the new interface is: if you define is_prime then my module will define predecessor_is_prime and successor_is_prime:

module Arithmetic
    function is_prime end
    predecessor_is_prime(x::MyAbstractNumber) = is_prime(x-1)
    sucessor_is_prime(x::MyAbstractNumber) = is_prime(x+1)
end

Is there a way to do this without breaking changes and without creating a dispatch loop that causes a StackOverflow error when none of the methods are defined for a concrete subtype?

(The real-world problem that motivates this is adding modify! and defining all other AbstractDict operations in terms of it, making the AbstractDict interface: implement modify! (and optionally iterate) and get everything else for free. Related to https://github.com/JuliaLang/julia/pull/33758)

One way is to also add a trait specifying which interface is implemented, and assume the old one by default. For example:

supports_is_prime(x) = false
supports_is_prime(x::MyAbstractNumber) = true
predecessor_is_prime(x) = supports_is_prime(x) ? is_prime(x-1) : throw(MethodError(...))
successor_is_prime(x) = supports_is_prime(x) ? is_prime(x+1) : predecessor_is_prime(x+2)
3 Likes