Interface guarantees

So in this entire thread in particular this post there is the notion that tests could replace formal interfaces.

At the moment intefaces are only defined in documentation and that leads to a lot of stability issues it seems. Now people could write test functions for their interfaces, but then you would still have to import those test functions as a user and use them in your test (which a lot of people won’t do by themselves).

So here is a suggestion to make this extremely convenient:

abstract type AbstractArray requires a
      @test_nowarn size(a)
      @test_nowarn keytype(a) # this is missing from the documentation
      idx = mock(keytype(a)) # but keytype this is not really possible
      @test_nowarn getindex(A, idx) 
mock m
      size(m) = 0
      keytype(m) = Int
      getindex(m, i) = i
end

the statement

struct MyArray <: AbstractArray
       x::AbstractTypeX
end

would use the mock definition of AbstractTypeX to construct a MyArray(mockTypeX) and then feed this object into the requires test of AbstractArray.

This test could be added automatically to ] test of the package and could be run by IDEs. I.e. it could be made available with

function interfrace_Test(::ConcreteType, ::AbstractType)
       m = mock(ConcreteType) # recursive on its fields
       requires(AbstractType)(m)
end

Types may define multiple mocks. The largest issue is probably ensuring that this is performant.

I seem to recall at least one similar project: GitHub - rafaqz/Interfaces.jl: Macros to define and implement interfaces, to ensure they are checked and correct.

Is it comparable? Did I miss others?

You reminded me of Towards the creation of an interface testing package, which both mentions other libraries and has a decently lengthy background discussion.

2 Likes

I never really did much with Interfaces.jl honestly, i have too many other packages and I dropped the ball after a few minor criticisms :sweat_smile:

I think it needs a lot of feedback and working over by more people to be usefull.

1 Like

Apologies if my criticism was part of the issue. I think its an important effort but there’s a lot to consider when making an interface to making interfaces. I’d be happy to comment if pinged on GitHub. I’ve been trying to get back in the habit of answering GitHub messages while on the bus so I’m not MIA

Yeah, as I said they were minor, and also probably valid. But overall I guessed the design just missed the mark, and maybe most designs will miss the mark for something like that. Its hard to justify working on that kind of speculative idea when there are so many more concrete projects with real users.

1 Like

There has also been GitHub - quinnj/Interfaces.jl: interfaces for Julia, but Interfaces.jl(by @Raf) seems more complete - and more substantial - both wrt. mandatory/optional parts of an interface - and going further than e.g. C#, i.e., just ensuring that proper methods are defined - by defining tests.

1 Like

FWIW I would be happy to do further work on Interfaces.jl, if other people were interested in joining the effort. But its really easy to take something like this in the wrong direction without users and code review.

I’m not attached to the details of the design except I think something like the mandatory/optional distinction is important for this to work in julia - as most interfaces get broken in various places. Its fine to have patchy implementations if we can query where they are patchy (like StaticArrays.jl not having setindex!) and Interfaces.jl gives you compile time traits to do that with.

(The other thing I really wanted out of the package was traits you can query at compile time to know if an object implements a function - e.g. to fix problems like being able to check if you can run issorted without just trying and catching the error)

4 Likes

@FelixBenning reading over your original post this really is pretty much what Interfaces.jl already does,
except it’s not tied to abstract types.

These days a lot of interfaces use traits rather than inheritance (think Tables.jl), so they need to be decoupled.

(So the problem is either making Interfaces.jl better, or just getting people to use it)

How should trait-based interfaces be represented in such format? For example Tables.jl which are not supposed to be an abstract type? I think the best minimal interface is just a single function signature :slight_smile:

The annoying thing is that neither Traits nor interfaces is really part of the standard language so not enough people use them causing the mentioned stability problems. And I am mostly a consumer of types - so I would really need AbstractArrays to implement them and the packages I use to use them. But I am glad that this is something people are working on.

@sairus7 Sorry maybe I don’t understand your point, but if you need an example Interfaces.jl already doesn’t care about inheritance, just functions defined on types. So it already works with traits.

You can use it on a single function signature, but in practice interfaces are usually groups of functions, some required and some optional.

@FelixBenning yes Julia has various shortcomings, so its best if we focus on what we can do about them. Proving a design in a package is the usual method for doing that.

And to be clear no-one is really working on this, it actually requires people like you (who care enough to start this conversation) to chip in, or it probably wont happen.

There is an implementation of the iteration spec sitting unused and unregistered in the BaseInterfaces.jl subpackage of Interfaces.jl:

The AbstractArray interface is an obvious next step.

The key thing missing is someone with the time to implement them, flesh out the package design, and make PRs to other packages that should use the interface to get the ball rolling. Again, without that this is a nice discussion, but in the end, just a discussion. PRs are the only thing that really exists in open source software.

How is that potentially possible?
Many functions, including issorted, depend on both the container properties (there should be some order of elements) and on properties of elements themselves (they should support comparison).

Man I don’t know, but at least one of those two is, and we can already check if the container is a AbstractArray.

And are you ever actually positive about anything on here?

Why do you think people stop working on projects like this - its just not worth these discussions, life is too short and I don’t care that much. I’m literally just trying to help the ecosystem to improve.

(but like, define a sortable interface trait, apply it to everything that defines isless ? I don’t get what the problem is)

2 Likes

There are a lot of cases where we want to know if it’s known to have a characteristic at compile time and then if not check it dynamically. In this case something like a range is clearly sorted but a Vector may not be. You need both approaches but it’s hard to convince people it’s worth maintaining two method that are so similar.

Just to be clear, the example @Raf gave was an interface requirement that tells you if it’s okay to run isssorted, he did not say anything about statically knowing if a container is sorted, just whether it’s possible to dynamically check if the container is sorted. Those are totally different things.

5 Likes

Sorry for the noise. So the big picture is knowing if you can run a method without it throwing an error because there’s not a valid definition for it, generic or otherwise?

1 Like

Thanks for at least a partial answer (:
It’s often best not to go looking for unintended subtext in very concrete questions. I thought you may already have an idea how this issorted example would actually look and when it gets checked, and simply asked about that. Sorry if that’s not the case.

Tone is difficult in writing, but I consider most of my messages pretty positive. Not sure if that’s relevant here in the abstract though.

Julia doesn’t have function signatures. It has, at most, method signatures. But even those are not useful for specifying interfaces in practice because the type system is not sophisticated enough.

I mean, what’s the signature of the map function in Julia?

@Raf I will have some resources to throw at this in the context of a project that will start early next year, unfortunately not earlier (funding in academia…). If that’s feasible for you I’ll be in touch.

The big picture was having compile-time traits for interface methods and their optional components, that are backed by tests. My example was just an quick attempt at an illustrating one case of needing a trait that I have hit in the past, its wasn’t important to this discussion.

You could use these traits to avoid errors, or to choose particular behaviors and optimizations. Like your traits in ArrayInterface.jl, but not just for arrays. Say if some iterable has Int getindex defined, you could skip collect in some circumstances, to make another toy example that isn’t worth poking holes in.

The central idea was making making clear statements about implementing interfaces, and explicitly connecting the traits of the interfaces to tests, in a granular way so parts of the interface can be ignored, as we tend to do. Currently we do these things in ad-hoc unstructured ways, like defining specific test functions for interfaces manually and hoping they get run.

Totally @FHell julia method “signatures” are loose and can change at run time… so we need another structure to lock down parts of them as having specific structures and reliable behavior. I look forward to seeing what you will do.

1 Like