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

Multiple Inheritance #43

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open

Multiple Inheritance #43

wants to merge 7 commits into from

Conversation

rafaqz
Copy link
Owner

@rafaqz rafaqz commented Mar 3, 2024

This PR implements multiple inheritance for Interface types storing the inherited types in a second Inherits type parameter of the supertype Interface, and adding dispatch on the second type parameter in the @implements macro so that inheriting types will have a fallback to the inherited types dispatch.

The Inherits parameter can be a Union, so dispatch will work for multiple inherited interfaces. We flatten the inheritance when interfaces are chained (like MyArrayInterface inherits ArrayInterface inherits IterationInterface) so everything is in one Union rather than nested. So the Inherits parameter will be Union{ArrayInterface,IterationInterface} or Union{ArrayInterface{(:some, :options)},IterationInterface}. But the options are stripped by inherited_basetype when Inherits is used for dispatch.

Needs docs and reorganisation/renaming of all the helper methods, it was hard to write so I imagine also hard to understand.

@gdalle if you have time for some feedback, that could help guiding how to document and explain this.

Closes #6

@rafaqz rafaqz requested a review from gdalle March 3, 2024 10:39
Copy link

codecov bot commented Mar 3, 2024

Codecov Report

Attention: Patch coverage is 34.54545% with 36 lines in your changes are missing coverage. Please review.

Project coverage is 74.23%. Comparing base (071d44f) to head (692b95b).

Files Patch % Lines
src/interface.jl 27.58% 21 Missing ⚠️
src/implements.jl 35.00% 13 Missing ⚠️
src/test.jl 66.66% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main      #43       +/-   ##
===========================================
- Coverage   85.11%   74.23%   -10.89%     
===========================================
  Files           5        5               
  Lines         215      260       +45     
===========================================
+ Hits          183      193       +10     
- Misses         32       67       +35     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@rafaqz
Copy link
Owner Author

rafaqz commented Mar 4, 2024

One caveat here I havent really thought through is that any kind of inheritance necessitates that tests of all inherited interfaces accept the same objects

Mostly I think that will be fine, but may hit problems where mulitiple arguments are used

@gdalle
Copy link
Collaborator

gdalle commented Mar 4, 2024

I'll check this out!

@gdalle
Copy link
Collaborator

gdalle commented Mar 18, 2024

Sorry I did not take the time, will make a pass this week

@rafaqz
Copy link
Owner Author

rafaqz commented Mar 18, 2024

No worries!

Copy link
Collaborator

@gdalle gdalle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel like I can review the internals, but I like the basic mechanism of flattening inheritance, and the way that the inheritance is specified.

Do we have problems if we try to dispatch on several interfaces at once?
For instance if I define a function f(x, y) and I want the first argument to implement ArrayInterface and the second DictInterface. I know it's been a problem for traits packages too (see https://github.com/mauro3/SimpleTraits.jl?tab=readme-ov-file#details-of-method-dispatch for example)

Comment on lines +11 to +12
@test supertype(ArrayInterface) <: Interfaces.Interface{<:Any,IterationInterface{(:reverse, :indexing)}}
@test supertype(DictInterface) <: Interfaces.Interface{<:Any,IterationInterface{(:reverse,)}}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@test supertype(ArrayInterface) <: Interfaces.Interface{<:Any,IterationInterface{(:reverse, :indexing)}}
@test supertype(DictInterface) <: Interfaces.Interface{<:Any,IterationInterface{(:reverse,)}}
@test supertype(ArrayInterface) <: Interfaces.Interface{<:Any,>:IterationInterface{(:reverse, :indexing)}}
@test supertype(DictInterface) <: Interfaces.Interface{<:Any,>:IterationInterface{(:reverse,)}}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in case we define more interfaces in the future and the second parameter is a union, this seems more robust?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also why not just test ArrayInterface <: ...?

@@ -201,4 +201,4 @@ array_components = (;

_wrappertype(A) = Base.typename(typeof(A)).wrapper

@interface ArrayInterface AbstractArray array_components "Base Julia AbstractArray interface"
@interface ArrayInterface <: IterationInterface{(:reverse,:indexing)} AbstractArray array_components "Base Julia AbstractArray interface"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interface definitions are becoming fairly long to put everything on a single line.
I remember we had discussed defining the documentation somewhere else and just giving the macro a String variable, but for some reason it didn't work?

implements(::Type{<:Interface}, obj::Type) = false
implements(T::Type{<:Interface}, obj::Type) = inherits(T, obj)

function inherits end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not qualified to review the exact implem but there are lots of codecov misses here

@@ -21,6 +21,24 @@ optional_keys(T::Type{<:Interface}) = keys(components(T).optional)

mandatory_keys(T::Type{<:Interface}, args...) = keys(components(T).mandatory)

function _flatten_inheritance(::Type{T}) where T
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here for the codecov misses, is it due to macros?

end
_check_no_options(T::Type) = T
_check_no_options(::Type{<:Interface{Keys}}) where Keys =
throw(ArgumentError("Interface options not accepted for more than one implementation"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does that mean?

@@ -141,7 +144,7 @@ function test(::Type{T}; show=true, kw...) where T
results = map(methodlist) do m
t = m.sig.parameters[2].var.ub
t isa UnionAll || return true
interface = t.body.name.wrapper
interface = t.body.body.name.wrapper
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recently discovered nameof, which might simplify this kind of stuff?

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 this pull request may close these issues.

Hierarchy of interfaces
2 participants