rafaqz / Interfaces.jl

Macros to define and implement interfaces, to ensure they are checked and correct.
MIT License
72 stars 4 forks source link

Hierarchy of interfaces #6

Open Tokazama opened 1 year ago

Tokazama commented 1 year ago

Do you have any suggestions on the best way to combine interfaces or make a hierarchy. For example, multidimensional indices could be thought of as a subset of the iteration interface. One might also want composite interfaces like those in BinaryTraits.jl

rafaqz commented 1 year ago

Good question! I've been thinking about it but haven't settled on something yet.

We have abstract types already, so traits can work based on subtyping.

All the test components of a parent type can be accessed with a function of the type.

So we can pass the supertupe to the macro and add the required parent tests to the child types test function.

Syntax could be like:

@interface ChildInterface <: ParentInterface{(:setindex,)} (
   # Child tests here
)

This could give us an inheritance system for the optional components too?

You can also just do inheritance manually by splatting the parent types named tuple of tests into your own tests, but you lose the trait inheritance.

Tokazama commented 1 year ago

I'm not sure the best syntax. I think the exact way you implement it will depend on what the end goal is here. If this is something that is supposed to be part of all interfaces at run time then it's going to take a lot of work and community buy-in. If it's something like Aqua.jl and every package could just have it as a test dependency, then we might not need to create a strict type hierarchy to support this.

rafaqz commented 1 year ago

Well, its really a precompile time thing ;)

It may never be that widespread as a runtime dependency. But I think some package that does this needs to be, so its an attempt in that direction at the least.

Using it in tests isn't aiming so high, although people can use it for that too.

But implementing inheritance won't be hard. I will look at it in the next few days.

Tokazama commented 1 year ago

The problem with the runtime aspect is that all we really want is a trait that gives a valid true or false on whether it prescribes to an interface. But we often want that to be known at compile time so a simple binary trait that assumes it is implemented correctly without testing that size, length, etc is actually returning integers or some other runtime dependent check. That's where I see something like this being really useful for tests. But I'm not the visionary here so I look forward to seeing what you come up with.

rafaqz commented 1 year ago

Thats what we have!

The trait is just a compile time binary that the interface is implemented. But the macro forces the trait to be tested during precompilation, so the only way for it to be wrong is to manually define the trait functions, which I hope will be discouraged.

Edit: I implemented single dispatch inheritance, I'll push later when I have wifi. But I'm wondering if we want multiple inheritance instead, so you can combine interfaces.

Tokazama commented 1 year ago

But testing that when loading the package is going to start adding a lot to compile time and initialize new methods in the method table, potentially causing invalidations in subsequent packages.

rafaqz commented 1 year ago

Yes, probably it will be too slow.

The option is to define the interface structure at runtime and fill it out in InterfaceTests.jl. That will just take a little longer to write 😅.

It will just need a Interfaces.test(Interface, MyType) call in testing.

And probably the tests need to be defined separately to the runtime, which all gets a little more painful to organise.

This kind of thing really isn't julias strong point, and trying to do it makes that clear. But thats part of the excercise I guess: what is the most we can hope for.

Tokazama commented 1 year ago

I'm not yet sold on this having a place in runtime code or package compilation, but I definitely can see the benefit of formally defining a set of tests that must pass for an interface to be correctly implemented. If that was the end goal it would be much easier to just build the interfaces with multiple levels and parents in a tree structure or list because performance wouldn't be critical.

rafaqz commented 1 year ago

There should be an easy way to have tests and traits compiled separately but have them linked.

Just defing the interface list components at runtime has no overhead.

Then we can define and run the tests with method dispatch wherever we need to. A big interface can have a separate test package, a small one wouldn't need to worry.

rafaqz commented 1 year ago

8. Running tests in the test suite is enough.