RequiredInterfaces.jl
RequiredInterfaces.jl copied to clipboard
RequiredInterfaces bricks usage of Revise
When using the Revise package in combination with the definition of an interface via RequiredInterfaces.@required changes in the source code immediately throws an error.
As an example I used a completely clean julia environment and added only the packages Revise and RequiredInterfaces, followed by loading Revise.
The test module is:
module TestModule
import RequiredInterfaces
abstract type AbstractDummyType end
RequiredInterfaces.@required AbstractDummyType begin
foo(::AbstractDummyType)
bar(::AbstractDummyType)
end
struct DummyType <: AbstractDummyType end
foo(::DummyType) = "foo"
bar(::DummyType) = "bar"
end
Calling Revise.includet("path/to/TestModule") throws the error
ERROR: LoadError: ArgumentError: `AbstractDummyType` is already registered as an interface.
Use the `begin` block version to specify multiple methods as part of the interface `AbstractDummyType`.
in expression starting at path/to/TestModule.jl:7
Stacktrace:
[1] var"@required"(__source__::LineNumberNode, __module__::Module, T::Symbol, expr::Expr)
@ RequiredInterfaces ...\.julia\packages\RequiredInterfaces\FQnDq\src\RequiredInterfaces.jl:47
[2] eval
@ .\boot.jl:430 [inlined]
[3] process_source!(mod_exprs_sigs::OrderedCollections.OrderedDict{Module, OrderedCollections.OrderedDict{Revise.RelocatableExpr, Union{Nothing, Vector{Any}}}}, ex::Expr, filename::String, mod::Module; mode::Symbol)
@ Revise ...\.julia\packages\Revise\mLfYT\src\parsing.jl:54
in expression starting at path/to/TestModule.jl:7
Unfortunately this makes development with Revise and RequiredInterfaces practically useless.
This is halfway between intentional behavior and a limitation of julia/Revise of only being able to evaluate entire files at once (at least through includet). @required is intended to only be called once on a given abstract type, because interfaces are not generally extensible after their definition without breaking the API/users (types that implement the interface between such @required definitions have no way of knowing about the increase in API surface, and as such can't implement it properly). This is why multiple invocations are disallowed. Revise however doesn't know this/can only reevaluate entire files at once, so even if the interface declaration doesn't change, it expands the macro into the same module again, encounters the existing state within, and thus ends up with an error. To RequiredInterfaces.jl, these situations are (to my knowledge) indistinguishable, as Revise doesn't provide an interface to detect this kind of situation.
The workaround is to put the interface definitions into a separate file, so they only get reevaluated when the interfaces actually change, at which point you'll have to fix your implementors anyway.
Thnks for this very clear explanation.
Totally makes sense to me. So probably a feasible workaround while developing the interface for the first time, would be to comment out the RequiredInterfaces part and once the interface is fully fixed it can be added.
And even if Revise would provide an interface (something like is_revised) this would probably require Revise to be included and thus be not a good ides for this lightweight package.
Anyways, would it be possible to add a part to the documentation that explains the Interplay with Revise and adds your suggested workaround? I guess that would be helpful for some others too.