-
Notifications
You must be signed in to change notification settings - Fork 219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Figure out a consistent and robust way of defining and testing interfaces #2434
Comments
It might be helpful: https://github.com/rafaqz/Interfaces.jl, but I haven't used it myself, so I'm not sure how mature it is. |
Indeed, Will brought this up before too and I've seen it mentioned once or twice on Discourse. I've read through the Interfaces.jl docs a couple of times now, and my assessment is that it's almost completely syntax sugar, in the sense that Interfaces.jl doesn't let you do very much that you can't already do by writing a standard test.* As a demonstration, here's a simplified version of the example in the Interfaces.jl docs: #################
# src/animal.jl #
#################
abstract type Animal end
function age end
function walk end
components = (
mandatory = (
age = (
"all animals have a `Real` age" => x -> age(x) isa Real,
),
),
optional = (
walk = "this animal can walk" => x -> walk(x) isa String,
),
)
description = """
Defines a generic interface for animals to do the things they do best.
"""
@interface AnimalInterface Animal components description
struct Duck <: Animal
age::Int
end
age(duck::Duck) = duck.age
walk(::Duck) = "waddle"
@implements AnimalInterface{(:walk,)} Duck [Duck(1), Duck(2)]
##################
# test/animal.jl #
##################
Interfaces.test(AnimalInterface) This is syntax sugar for the following: #################
# src/animal.jl #
#################
abstract type Animal end
function age end
function walk end
"""
Test that animals do the things they do best.
"""
function test_animal_interface(a::Animal, optional::Tuple{Symbol}=())
test_age(a)
:walk in optional && test_walk(a)
end
test_age(a::Animal) = @test age(a) isa Real
test_walk(a::Animal) = @test walk(a) isa String
struct Duck <: Animal
age::Int
end
age(duck::Duck) = duck.age
walk(::Duck) = "waddle"
##################
# test/animal.jl #
##################
@testset for duck in [Duck(1), Duck(2)]
test_animal_interface(duck, (:walk,))
end I find the latter more readable because there is one less layer of indirection. You don't need to go and read up about how an additional package works. Also, if you want to modify the behaviour of the tests, you also aren't constrained by the limitations of the package. For example, Interfaces.jl's interface gets a little bit ugly when you want to use test functions which take multiple arguments. You have to pass in an Lastly, sometimes there are interfaces that aren't based on abstract types. The example I have off the top of my head are the demo models in DynamicPPL, which all have type * There is indeed one nice thing that Interfaces.jl lets you do, which is the tl;drHaving surveyed a few corners of Julia land, I kinda believe the best way is still to just stick to basic, understandable testing functions. Also, for any abstract type we should have a minimal test double as demonstrated in the Invenia interfaces blog post. And finally I think abstract types, their interfaces, and their test doubles should always live in a single file with nothing else in them. Basically, don't mix interfaces and implementations in the same file. Julia lets us split files up as you like, so we should make use of that to make the code as clear as possible. I think the best place to start with this is |
We have a lot of interfaces across the Turing ecosystem. Few of them are formalised or testable. This is partly a result of Julia's multiple dispatch mechanism: it's possible to define
InterfacePackage.foo()
with whatever signature we like, and then overload it inImplementationPackage
with a completely different signature. Thus, when implementing a function, there's actually zero need to pay attention to what the interface demands.The problem with this is that:
There aren't really any amazing solutions to this in Julia. We should try to figure out what the least bad solution is.
The text was updated successfully, but these errors were encountered: