Skip to content
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

Usage for Multiple (but different) input types? #49

Closed
Datseris opened this issue Jul 13, 2024 · 3 comments · Fixed by #50
Closed

Usage for Multiple (but different) input types? #49

Datseris opened this issue Jul 13, 2024 · 3 comments · Fixed by #50

Comments

@Datseris
Copy link

Datseris commented Jul 13, 2024

Hi there, thanks for the effort. The typical situation in both my code development as well as scientific workflows is that my interfaces (specifically, my APIs) are functions of two input arguments, that are also of a fundamentally different type hierarchy.

I think the simplest conceptual example is the DynamicalBilliards.jl project. There I have Particles that follow a type hierarchy and have singular methods f(x::Particle). I also have Obstacles that also follow a type hierarchy and have singular methods g(y::Obstacle). However, the majority of the software is functions q(p::Particle, o::Obstacle), that truly dispatch along both types. A concrete example is the collision function.

My question is, how would I use this package to setup tests for this interface? It is not clear to me. I saw the example on the Number in the docs, that allows functions with two inputs, but the inputs are all part of the same type hierarchy. Perhaps you can write a tutorial/doc example of how to use Interfaces.jl, if possible, to make the interface composed of two type hierarchies and the following three functions:

abstract type Obstacle end
abstract type Particle end

function normalvec(o::Obstacle) end # returns `SVector{2, T<:Real}` with norm 1
function velocity(p::Particle) end # returns `SVector{2, T<:Real}` with norm 1
function collision_time(p::Particle, o::Obstacle) end # returns `T::Real`
@rafaqz
Copy link
Owner

rafaqz commented Jul 13, 2024

Thanks, yes its an interesting use case.

Probably you need ObstacleInterface and ParticleInterface to be different things, and in them test againsts each other.

We'll need to add a single argument version of test_objects that takes the interface type and returns a vector of all the objects of all the types that implement the interface. Requires some ugly introspection of the method table for test_objects, but we already do that in tests here.

This example could work:

particle_components = (;
    mandatory=(;
        normalvec = (
            "returns `SVector{2, T<:Real}`" => x -> normalvec(x) isa  SVector{2, T<:Real},
            "has norm 1" => x -> ..., 
        ),
        velocity = "label" => func
        collision_time = (
            "returns a Real in collisions with SomeDefaultObstacle" = x -> collision_time(x, SomeDefaultObstacle()) isa Real,
            "returns a Real in collisions with all available obstacles" => x -> begin
                map(values(test_objects(ObstacleInterface))) do obstacle
                    collision_time(x, obstacle) isa Real
                end |> all
            end
        )
    ),
    optional = (;
        something_optional = "label" => test,
    )
)
@interface ParticleInterface Particle particle_components "Particle interface docs"
@implements ParticleInterface MyParticle [MyParticle(1, 2), MyParticle(1.0, 2.0)]

obstacle_components = (;
    mandatory=(;
        normalvec = (
            "returns `SVector{2, T<:Real}`" => x -> normalvec(x) isa  SVector{2, T<:Real},
            "has norm 1" => x -> ..., 
        ),
        velocity = "label" => func
        collision_time = (
            "returns a Real in collisions with SomeDefaultParicle" = x -> collision_time(SomeDefaultParticle(), x) isa Real,
            "returns a Real in collisions with all available particles" => x -> begin
                map(values(test_objects(ParticleInterface))) do particle
                    collision_time(particle, x) isa Real
                end |> all
            end
        )
    )
    optional = (;
        something_optional = "label" => test,
    )
)
@interface ObstacleInterface Obstacle components "Obstacle interface docs"
@implements ObstacleInterface MyObstacle [MyObstacle(1, 2), MyObstacle(1.0, 2.0)]

Then

Interfaces.test(ParticleInterface, MyParticle)
Interfaces.test(ObstacleInterface, MyObstacle)

@Datseris
Copy link
Author

That seems fine to me!

@rafaqz
Copy link
Owner

rafaqz commented Jul 14, 2024

This should work now. Ive updated the example above to handle the Dict return value of test_objects.

Its still slightly pseudo-code but with customisation for you interface it should work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants