Throwing my own `NotImplementedError` to emulate abstract methods

Hi there

I want to write a piece of software that my users can extend by writing “plugins” for it. Specifically, they would provide their own types in their plugins, that the main program will then operate on. For the main program to be able to do that the provided types need to adhere to a well defined interface. I think that’s a setting many of us have encountered before.

In a language like, say, Python an abstract base class that people can inherit from would probably be the most common way to go about that. Now, in Julia, in order to at least document what that interface is I’m tempted to write

computeOutputsAfter(unit::Unit, timestep::TimestepInH, inputs::Inputs) = throw NotImplementedError()  #  returns Outputs
setInputs!(unit::Unit, inputs::Inputs) = throw NotImplementedError()  # returns Nothing

where Unit is the base type the plugin types extend, the two functions are the ones that need to be defined on each plugin type and NotImplementedError I’d defined myself in analogy to Python. Unfortunately, the expected return type can only be given as a comment AFAIK. Anyway, I find the above preferable over

function computeOutputsAfter end
function setInputs! end

– which I’ve seen mentioned in the documentation – because there I don’t even see the required number of arguments, let alone their type.

My question is: does that look like a reasonable approach or are there more common/idiomatic ways to do this in Julia.

Cheers,
Damian

I would say don’t define the methods for the base (“abstract”) type. That way Julia will throw an error when a given plugin does not satisfy the required interface.

Make sure to document what interface is expected though.

This reminds me that such an implementation is considered and anti-pattern in JuliaLang Antipatterns .

Of course, one could say that it is a matter of taste, but coming from such an experienced julia programmer it may be worth to take it into account.

1 Like

The thing that has bitten me over and over with this is that when you define the method generically with a NotImplementedError, you can end up confusing dispatch if you want to, say, overload the method with something more specific for unit but more general for intputs. Then Julia will say that it doesn’t know which method to use.

So I’ve just learned the hard way to just do

function computeOutputsAfter end

with a comment for the type signature.

Julia has such a great type system that you want to use it as much as possible. But really, the type system is not a module system or a correctness system; it’s a dispatch and performance system. Having those methods defined to throw NotImplementedError doesn’t help with dispatch and doesn’t help with performance, and so isn’t really an idiomatic usage of the type system.

5 Likes

I find the following blog post to be the most relevant and interesting resource for issues related to the need for interfaces in real-world applications:

1 Like

That’s a good explanation, thanks! Where would that bare-bones “implementation” implementation typicall be put? In something like unit_base.jl?

Actually, could anyone point me to a project where something like is done, ideally to the documentation explaining what the expected signature is?

In reply to my own post, @aramirezreyes, already provided such an example here: JuliaLang Antipatterns

At its core, a MethodError only says “there is no method matching these arguments”; it doesn’t communicate whether there should be such a method or not. This is why I disagree with the idea of just leaving the base function completely unimplemented. You don’t want to end up in a situation where a user implements your interface wrongly and tries to “fix” that by chasing MethodErrors.

I’m the maintainer of RequiredInterfaces.jl, which does exactly that. I use this package for pretty much all of my projects, where I want to provide a user-extensible API.

See also this github issue for more discussion about NotImplementedError:

From my POV, this should just be part of Base.