# Module that defines an interface for an abstract type without implementing it

**URL:** https://discourse.julialang.org/t/module-that-defines-an-interface-for-an-abstract-type-without-implementing-it/113624
**Category:** General Usage
**Created:** [April 29, 2024, 6:51pm UTC](https://discourse.julialang.org/t/module-that-defines-an-interface-for-an-abstract-type-without-implementing-it/113624 "2024-04-29T18:51:28Z")
**Posts on this page:** 12
**Page:** 1

<div class="post-metadata">

### Author: ![maxkapur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/maxkapur/32/21208_2.png) [@maxkapur](https://discourse.julialang.org/u/maxkapur)
#### Post date: [April 29, 2024, 6:51pm UTC](https://discourse.julialang.org/t/module-that-defines-an-interface-for-an-abstract-type-without-implementing-it/113624/1 "2024-04-29T18:51:28Z")

</div>

Suppose I have a module that defines an abstract type `AbstractPerformer` (as in, a stage performer) and a stage routine for the performer as follows:

```julia
module Performers

abstract type AbstractPerformer end

function rejoice(ap::AbstractPerformer)
    smile(ap)
    say(ap, "Oh joy!")
end

end # module

```

The important point here is that I don’t define the functions `smile()` and `say()` myself. I want users of my module to define their own concrete type under `AbstractPerformer` and implement these two functions themselves—and thereby get the `rejoice()` function for their type “for free.”

Here’s what I’ve tried:

1. Do nothing. This doesn’t work because the user has to be able to extend the `smile()` and `say()` symbols referred to in `rejoice()`.

2. Provide “dummy” implementations within the module such as

3. Define “empty” symbols such as:

Is there a better way?

Pardon typos; am on mobile.

---

<div class="post-metadata">

### Author: ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)
#### Post date: [April 29, 2024, 7:30pm UTC](https://discourse.julialang.org/t/module-that-defines-an-interface-for-an-abstract-type-without-implementing-it/113624/2 "2024-04-29T19:30:11Z")

</div>

> [@maxkapur](#):
>
> the call to these functions just throws a bare `MethodError`.

I would use these dummy functions but instead of throwing a general error, throw a very clear error message instructing the user on what has to be implemented.

---

<div class="post-metadata">

### Author: ![kellertuer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kellertuer/32/220707_2.png) [@kellertuer](https://discourse.julialang.org/u/kellertuer)
#### Post date: [April 29, 2024, 7:43pm UTC](https://discourse.julialang.org/t/module-that-defines-an-interface-for-an-abstract-type-without-implementing-it/113624/3 "2024-04-29T19:43:41Z")

</div>

I personally prefer method 3 due to the Method Error providing a detailed error (if you just are off “a bit” from the original signature). A generic fallback with an error message would remove that opportunity.

One thing you can do – but I just remembered that was done in one (of my) packages (but of a co-developer of mine) is to register an error hint.

> <https://github.com/JuliaManifolds/Manifolds.jl/blob/32744d68eaf51bf7b2ae962ae6539df6df9a3b55/src/Manifolds.jl#L582-L589>

This one adds a hint to the `MethodError` that the user probably forgot to load a package, since the function we test for here is only available/defined in an Extension. I feel this would work best with 3. Also Method 3 allows the user to import your functions to extend them, which is a nice and clean way to do so.

---

<div class="post-metadata">

### Author: ![maxkapur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/maxkapur/32/21208_2.png) [@maxkapur](https://discourse.julialang.org/u/maxkapur)
#### Post date: [April 29, 2024, 7:56pm UTC](https://discourse.julialang.org/t/module-that-defines-an-interface-for-an-abstract-type-without-implementing-it/113624/4 "2024-04-29T19:56:01Z")

</div>

The problem isn’t (just) that the bare `MethodError` is uninformative; it’s that the existence of the `say(::AbstractPerformer, ::Any)` method serves as a catchall that overrides type logic that may be specific to the user’s implementation.

E.g., suppose my module provides

```julia
julia> abstract type AbstractPerformer end

julia> foo(::AbstractPerformer, ::Any) = throw("This is a fallback method for AbstractPerformer. You should implement this function for your own concrete type")
foo (generic function with 1 method)

```

without taking a position on the type of the second argument to `foo()`.

But for a user, they may want to constrain their implementation of `foo(`) further:

```julia
julia> struct Performer <: AbstractPerformer end

julia> foo(::Performer, ::AbstractFloat) = "Desired result"
foo (generic function with 2 methods)

```

But now, if someone accidentally puts in an integer instead of a float, they get the OG throw from `foo(::AbstractPerformer, ::Any)`:

```julia
julia> foo(Performer(), 1)
ERROR: "This is a fallback method for AbstractPerformer. You should implement this function for your own concrete type"
Stacktrace:
 [1] foo(::Performer, ::Int64)
   @ Main ./REPL[2]:1
 [2] top-level scope
   @ REPL[5]:1

```

It would be more helpful if this call behaved the way it does when `foo(::AbstractPerformer, ::Any)` is undefined (but `foo(::Performer, ::AbstractFloat)` is):

```julia
julia> abstract type AbstractPerformer end

julia> struct Performer <: AbstractPerformer end

julia> foo(::Performer, ::AbstractFloat) = "Desired result"
foo (generic function with 1 method)

julia> foo(Performer(), 1)
ERROR: MethodError: no method matching foo(::Performer, ::Int64)

Closest candidates are:
  foo(::Performer, ::AbstractFloat)
   @ Main REPL[3]:1

Stacktrace:
 [1] top-level scope
   @ REPL[4]:1

```

(note the “closest candidates” hint)

---

<div class="post-metadata">

### Author: ![maxkapur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/maxkapur/32/21208_2.png) [@maxkapur](https://discourse.julialang.org/u/maxkapur)
#### Post date: [April 29, 2024, 8:05pm UTC](https://discourse.julialang.org/t/module-that-defines-an-interface-for-an-abstract-type-without-implementing-it/113624/5 "2024-04-29T20:05:59Z")

</div>

Maybe I am being a perfectionist here, but here’s how approach #3 looks in my IDE:

![image](https://global.discourse-cdn.com/julialang/original/3X/9/2/92247b6942617b88de3255249e5bca493e238972.png)

![image](https://global.discourse-cdn.com/julialang/original/3X/0/0/0083df0151b28217c34913b743650d3ca4d0cc01.png)

I don’t like this, because it makes it hard to distinguish genuine method call errors (e,g, `say(a, b, c)`) from these false positives.

* * *

At the risk of being tedious, the Python code below solves all of my problems (well, most of them—the float vs. int thing would require type annotations):

```python
class PerformerInterface:

    def rejoice(self):
        self.smile()
        self.say("Oh joy!")

    def smile(self):
        raise NotImplementedError

    def say(self, words):
        raise NotImplementedError

class Performer(PerformerInterface):

    def smile(self):
        print(":)")
    
    def say(self, words):
        print(f"I have something to say: {words}")

```

---

<div class="post-metadata">

### Author: ![gdalle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdalle/32/27854_2.png) [@gdalle](https://discourse.julialang.org/u/gdalle)
#### Post date: [April 29, 2024, 8:34pm UTC](https://discourse.julialang.org/t/module-that-defines-an-interface-for-an-abstract-type-without-implementing-it/113624/6 "2024-04-29T20:34:59Z")

</div>

I agree that an empty function `function smile end` is the best approach because a `MethodError` is semantically correct here. If you need additional specification beyond what’s in the docs, take a look at Interfaces.jl or RequiredInterfaces.jl

---

<div class="post-metadata">

### Author: ![Krastanov](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/krastanov/32/6817_2.png) [@Krastanov](https://discourse.julialang.org/u/Krastanov)
#### Post date: [April 29, 2024, 8:49pm UTC](https://discourse.julialang.org/t/module-that-defines-an-interface-for-an-abstract-type-without-implementing-it/113624/7 "2024-04-29T20:49:12Z")

</div>

To add to what @gdalle mentioned, if you use a MethodError you get a semantically correct error **AND** you can add a lot of extra information and explanations to it. You can register a “hint” that explains that this is an informal abstract method and provide guidance to the user or developer.

```julia
help?> Base.Experimental.register_error_hint
  Experimental.register_error_hint(handler, exceptiontype)

  Register a "hinting" function handler(io, exception) that can suggest
  potential ways for users to circumvent errors. handler should examine
  exception to see whether the conditions appropriate for a hint are met, and
  if so generate output to io. Packages should call register_error_hint from
  within their __init__ function.

  For specific exception types, handler is required to accept additional
  arguments:

    • MethodError: provide handler(io, exc::MethodError, argtypes,
       kwargs), which splits the combined arguments into positional and
       keyword arguments.

```

---

<div class="post-metadata">

### Author: ![sadish-d](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sadish-d/32/48058_2.png) [@sadish-d](https://discourse.julialang.org/u/sadish-d)
#### Post date: [April 30, 2024, 2:42am UTC](https://discourse.julialang.org/t/module-that-defines-an-interface-for-an-abstract-type-without-implementing-it/113624/8 "2024-04-30T02:42:15Z")

</div>

I don’t really know how to write macros, but something like this could work.

```julia
macro todo(expr)
    @assert expr.head in (:call, :function)

    return quote
        $(esc(expr)) = error($(esc("implement this: " * string(expr))))
    end
end

@todo f(a::Int)

julia> f(1)
ERROR: implement this: f(a::Int)

```

But Interface.jl and RequiredInterfaces.jl look cool.

---

<div class="post-metadata">

### Author: ![kellertuer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kellertuer/32/220707_2.png) [@kellertuer](https://discourse.julialang.org/u/kellertuer)
#### Post date: [April 30, 2024, 4:33am UTC](https://discourse.julialang.org/t/module-that-defines-an-interface-for-an-abstract-type-without-implementing-it/113624/9 "2024-04-30T04:33:11Z")

</div>

Yes, the idea of relying on the `MethodError` is exactly to get the hint that the integer might have been wrong and the float one is the one closest, that the MethodError hints to.

Similarly if the user confuses two.  
Initiale we had quite a few throwing fallbacks. By now we removed all of them.

Concerning the IDE, for our cases, adding doc strings to the specific methods (but not implementing them) helped for most cases, see for example something like

> <https://github.com/JuliaManifolds/ManifoldsBase.jl/blob/830c5a1189b0ad5e20f23f4e62079823e49af536/src/exp_log_geo.jl#L26-L39>

but sure the Linter is not yet perfect there within VS Code.

---

<div class="post-metadata">

### Author: ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)
#### Post date: [April 30, 2024, 5:30am UTC](https://discourse.julialang.org/t/module-that-defines-an-interface-for-an-abstract-type-without-implementing-it/113624/10 "2024-04-30T05:30:22Z")

</div>

As the discussion above shows, we’re still figuring this out. Many would prefer to have some kind of interface check through some kind of static analysis. Honestly, we have made the interface checks too complicated thus far, while I also think the `MethodError` approach is a bit too passive.

Rather than passively wait for `MethodErrors` to arise, we should preemptively and simply test the interface. I’m starting to think we can use the dynamic features of Julia to test.

My proposal for interface testing is that one should provide a function to test the interface when declaring such an interface. Focus on `testAbstractPerformerInterface` below.

```julia
module Performers
    using Test

    export AbstractPerformer, rejoice

    abstract type AbstractPerformer end

    function smile end
    function say end

    function rejoice(ap::AbstractPerformer)
        smile(ap)
        say(ap, "Oh joy!")
    end

    function testAbstractPerformerInterface(ap::AP) where AP <: AbstractPerformer
            @testset "Testing if $AP implements AbstractPerformerInterface" begin
                @test hasmethod(smile, Tuple{AP})
                @test hasmethod(say, Tuple{AP, String})
                @test isnothing(smile(ap))
                @test isnothing(say(ap, "Oh joy!"))
            end
        end
    end

    # ... insert macro here ...

end # module Performers

```

An implementing type could do the following.

```julia
using Performers
struct MyPerformer <: AbstractPerformer end
Performers.testAbstractPerformerInterface(MyPerformer())
...
Test Summary: | Fail Error Total Time
Testing if MyPerformer implements AbstractPerformerInterface | 2 2 4 2.7s
ERROR: Some tests did not pass: 0 passed, 2 failed, 2 errored, 0 broken.

```

We could even provide some macro and preferences support so the tests do not have to run every time.

```julia
module Performers
    # ... see above

    # macro support
    using Preferences
    const abstract_performer_tests = @load_preference("abstract_performer_tests", false)

    macro implementsAbstractPerformer(ex, T)
        T = esc(T)
        quote
            $(esc(ex))
            if abstract_performer_tests
                testAbstractPerformerInterface($T)
            end
        end
    end

end # module Performers

```

An implementing type could the be declared as follows.

```julia
using Performers
Performers.@implementsAbstractPerformer begin
    struct MyPerformer <: AbstractPerformer end
    Performers.smile(::MyPerformer) = println("*smiles*")
    Performers.say(::MyPerformer, s::String) = println(s)
end MyPerformer()

```

The above will do nothing extra by default. However, during testing or development we could turn the tests on.

```julia
julia> using Preferences

julia> Preferences.set_preferences!("Performers", "run_abstract_performer_tests" => true, force=true)

julia> using Performers

julia> Performers.@implementsAbstractPerformer begin
           struct MyPerformer <: AbstractPerformer end
           Performers.smile(::MyPerformer) = println("*smiles*")
           Performers.say(::MyPerformer, s::String) = println(s)
       end MyPerformer();
*smiles*
Oh joy!
Test Summary: | Pass Total Time
Testing if MyPerformer implements AbstractPerformerInterface | 4 4 0.0s

```

For an incorrect implementation, we would get the following.

```julia
julia> Performers.@implementsAbstractPerformer begin
           struct BadPerformer <: AbstractPerformer end
           Performers.smile(::BadPerformer) = println("*smiles*")
           Performers.say(::BadPerformer) = "Hello World!"
       end BadPerformer();
Testing if BadPerformer implements AbstractPerformerInterface: Test Failed at /home/mkitti/blah/Performers.jl/src/Performers.jl:20
  Expression: hasmethod(say, Tuple{AP, String})

Stacktrace:
...
*smiles*
Testing if BadPerformer implements AbstractPerformerInterface: Error During Test at /home/mkitti/blah/Performers.jl/src/Performers.jl:22
  Test threw exception
  Expression: isnothing(say(ap, "Oh joy!"))
  MethodError: no method matching say(::BadPerformer, ::String)
  
  Closest candidates are:
    say(::BadPerformer)
     @ Main REPL[4]:4
  
  Stacktrace:
  ...
Test Summary: | Pass Fail Error Total Time
Testing if BadPerformer implements AbstractPerformerInterface | 2 1 1 4 0.2s
ERROR: Some tests did not pass: 2 passed, 1 failed, 1 errored, 0 broken.

```

One aspect that I like about the above is that we’re actively declaring what we think implements the interface.

In summary, if you want someone to implement a particular interface, provide a test function to check if they actually did implement that interface.

---

<div class="post-metadata">

### Author: ![gdalle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdalle/32/27854_2.png) [@gdalle](https://discourse.julialang.org/u/gdalle)
#### Post date: [April 30, 2024, 5:43am UTC](https://discourse.julialang.org/t/module-that-defines-an-interface-for-an-abstract-type-without-implementing-it/113624/11 "2024-04-30T05:43:28Z")

</div>

> [@mkitti](#):
>
> My proposal for interface testing is that one should provide a function to test the interface when declaring such an interface.

That is exactly what Interfaces.jl does, take a look at the [documentation](https://rafaqz.github.io/Interfaces.jl/stable/) for more details.

I have succesfully used it in GraphsInterfaceChecker.jl for the JuliaGraphs ecosystem

ping @Raf

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [April 30, 2024, 7:56am UTC](https://discourse.julialang.org/t/module-that-defines-an-interface-for-an-abstract-type-without-implementing-it/113624/12 "2024-04-30T07:56:54Z")

</div>

Relevant Julia issue:

> <https://github.com/JuliaLang/julia/issues/50196>
>
> Currently, defining some form of API like so:
> 
> \`\`\`julia
> abstract type Foo end…
> 
> """
> bar(::Foo)
> 
> Implements \`bar\` for subtypes of \`Foo\`. Needs to be implemented to fulfill the interface expected of \`Foo\`.
> """
> function bar end
> \`\`\`
> 
> always throws a \`MethodError\`. From a user-perspective, it's hard to find out whether \`bar\` was supposed to be implemented by a package they're using or not - it could very well just be a bug, since all \`MethodError\` communicates is "There is no method matching this". Since this can bubble up from deep in some library, it'd be better for users to be able to say "I got this \`NotImplementedError\`", which also makes it clear to package maintainers that they forgot to implement some method that is required of the interface they claim to implement.
> 
> This feature request/proposal is aimed at solving this issue, by giving package authors & Base an option to communicate to users "There is no fallback definition, since your type should implement this". This is done with a new error type, \`NotImplementedError\`, which is defined like so:
> 
> \`\`\`julia
> struct NotImplementedError \<: Exception
> interface::String
> func::String
> end
> 
> function Base.showerror(io::IO, nie::NotImplementedError)
> printstyled(io, "NotImplementedError: "; color=:red)
> print(io, "The called method is part of a fallback definition for the \`", nie.interface, "\` Interface.\\n",
> "Please implement \`", nie.func, "\` for your type T.")
> end
> \`\`\`
> 
> and used like so:
> 
> \`\`\`julia
> julia\> abstract type Foo end
> 
> julia\> bar(::Foo, ::Int) = throw(NotImplementedError("Foo", "bar(::T, ::Int)"))
> 
> julia\> struct Baz \<: Foo end
> 
> julia\> bar(Baz(), 1)
> ERROR: NotImplementedError: The called method is part of a fallback definition for the \`Foo\` Interface.
> Please implement \`bar(::T, ::Int)\` for your type T.
> Stacktrace:
> \[1\] bar(::Baz, ::Int64)
> @ Main ./REPL\[4\]:1
> \[2\] top-level scope
> @ REPL\[6\]:1
> \`\`\`
> 
> !\[image\](https://github.com/JuliaLang/julia/assets/11753998/e7a02901-95e2-44e7-9ba1-13b18b812d10)
> 
> This allows the distinction between intended-to-be-extended API (\`bar(::Foo, ::Int)\`) and this-is-supposed-to-error (any other signature on \`bar\`, which throws a \`MethodError\`).
> 
> \# To be discussed
> 
> The details of what exactly \`NotImplementedError\` should contain, since this version with just two strings is IMO too bare-bones, as it requires discipline/good grasp of the intended API to be able to create the string directly.
> 
> \# Why is this needed?
> 
> This error and variations on it are \[widespread\](https://juliahub.com/ui/Search?q=struct%20.%2aImplemented.%2aError&type=code&r=true) throughout the ecosystem and I think Base could benefit from having some fallback definitions like the one above as well, to give much more informative error messages when subtyping e.g. \`\<: AbstractArray\` or other abstract types in Base.
