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

Moved test objects to test method #10

Merged
merged 1 commit into from
May 29, 2023

Conversation

stemann
Copy link
Contributor

@stemann stemann commented May 29, 2023

As discussed in #9

@stemann stemann force-pushed the feature/move_test_objects branch 2 times, most recently from 4b3a189 to 3e196e6 Compare May 29, 2023 20:49
@codecov-commenter
Copy link

codecov-commenter commented May 29, 2023

Codecov Report

Merging #10 (8dc5408) into main (15cd55f) will decrease coverage by 4.13%.
The diff coverage is 75.00%.

@@            Coverage Diff             @@
##             main      #10      +/-   ##
==========================================
- Coverage   81.48%   77.35%   -4.13%     
==========================================
  Files           3        4       +1     
  Lines         108      106       -2     
==========================================
- Hits           88       82       -6     
- Misses         20       24       +4     
Impacted Files Coverage Δ
src/test.jl 77.77% <62.50%> (-6.64%) ⬇️
src/implements.jl 81.25% <100.00%> (+7.33%) ⬆️

... and 2 files with indirect coverage changes

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@rafaqz
Copy link
Owner

rafaqz commented May 29, 2023

Thanks. It definitely feels slicker to move those definitions into the test function and keep the declaration lean.

I'm just wondering if we lose anything?

I think my original idea was that a user could run test(TheInterface, TheObjectType) without needing to construct test objects. But probable that's an extremely rare situation in practice.

If you cant think of any other downside to this, lets merge it.

@stemann stemann force-pushed the feature/move_test_objects branch from 3e196e6 to 8dc5408 Compare May 29, 2023 21:40
@stemann
Copy link
Contributor Author

stemann commented May 29, 2023

Thanks. It definitely feels slicker to move those definitions into the test function and keep the declaration lean.

I'm just wondering if we lose anything?

I think my original idea was that a user could run test(TheInterface, TheObjectType) without needing to construct test objects. But probable that's an extremely rare situation in practice.

If you cant think of any other downside to this, lets merge it.

I think it is better to require that test objects are provided/created with the tests.

It could be encouraged that packages somehow make test values/objects available for themselves and for downstream packages if that was/is your concern? I.e. was your concern with the ease of interface testing wrt. sub-types of a hierarchy where an interface is defined for a supertype?

@stemann
Copy link
Contributor Author

stemann commented May 29, 2023

I think you were right saying (in #9) that the interface definitions should work on types - not values/objects. There's still a few mentions of objects - that could probably be cleaned up in a follow-up.

@stemann
Copy link
Contributor Author

stemann commented May 29, 2023

Also, JET.report_package(; target_defined_modules = true) is not completely happy with the calls to components(T) - not sure why...

@rafaqz
Copy link
Owner

rafaqz commented May 29, 2023

Thanks for checking JET. Can you post the output?

Probably my concern over test objects isn't that important for now, and we should follow YAGNI.

@stemann
Copy link
Contributor Author

stemann commented May 29, 2023

Thanks for checking JET. Can you post the output?

Probably my concern over test objects isn't that important for now, and we should follow YAGNI.

Probably not related to this branch:

═════ 4 possible errors found ═════
┌ @ /Users/jsa/.julia/dev/Interfaces/src/interface.jl:20 Interfaces.components(T)
│ no matching method found components(::Type{<:Interfaces.Interface}): Interfaces.components(T::Type{<:Interfaces.Interface})
└────────────────────────────────────────────────────────
┌ @ /Users/jsa/.julia/dev/Interfaces/src/interface.jl:22 Interfaces.components(T)
│ no matching method found components(::Type{<:Interfaces.Interface}): Interfaces.components(T::Type{<:Interfaces.Interface})
└────────────────────────────────────────────────────────
┌ @ /Users/jsa/.julia/dev/Interfaces/src/test.jl:39 Interfaces.components(T)
│ no matching method found components(::Type{<:Interfaces.Interface}): Interfaces.components(T::Type{<:Interfaces.Interface})
└───────────────────────────────────────────────────
┌ @ /Users/jsa/.julia/dev/Interfaces/src/test.jl:48 Interfaces.components(T)
│ no matching method found components(::Type{<:Interfaces.Interface}): Interfaces.components(T::Type{<:Interfaces.Interface})
└───────────────────────────────────────────────────

@rafaqz
Copy link
Owner

rafaqz commented May 29, 2023

Oh right JET wants us to define a method for the Interface supertype. I guess that could just return false to make it happy.

@stemann
Copy link
Contributor Author

stemann commented May 29, 2023

Yet another consideration following this change: I think I would prefer if the arguments for @implements and implements were ordered in reverse: @implements(type, interface_type) - as in https://github.com/quinnj/Interfaces.jl/blob/main/test/runtests.jl#L38

@rafaqz
Copy link
Owner

rafaqz commented May 29, 2023

Haha I guess that's a matter of taste, maybe I think about it the other way around.

If you think its better, I'll merge the PR, clearly Jacob thinks its better too.

@rafaqz rafaqz merged commit f52e483 into rafaqz:main May 29, 2023
@rafaqz
Copy link
Owner

rafaqz commented May 29, 2023

Also I find it hilarious how similar @quinnj s implementation is - I didn't read it before writing this. Some differences in design, but all the same function, types and macros, how is that possible

@stemann
Copy link
Contributor Author

stemann commented May 29, 2023

He he - that's funny indeed :-)

But isn't (at least) the interface definition a bit different in only declaring method signatures? Where the predicate approach can check much more complex interactions?

@interface Iterable begin

    iterate(::Iterable)
    iterate(::Iterable, st)

    Base.IteratorSize(::Type{Iterable})::Union{Base.HasLength, Base.HasShape, Base.IsInfinite, Base.SizeUnknown}

    if Base.IteratorSize(Iterable) == Base.HasLength()
        length(::Iterable)
    elseif Base.IteratorSize(Iterable) isa Base.HasShape
        length(::Iterable)
        size(::Iterable)
    end

    Base.IteratorEltype(::Type{Iterable})::Union{Base.HasEltype, Base.EltypeUnknown}

    if Base.IteratorEltype(Iterable) == Base.HasEltype()
        eltype(::Iterable) || eltype(::Type{Iterable})
    end

end

@rafaqz
Copy link
Owner

rafaqz commented May 29, 2023

I really havent read it as closely as you, I meant on a really surface level scan of the code.

That syntax does look nice though... but do you mean it cant actually check return values?

I thought the main difference overall was that my version gives other packages a breakdown of the implementation of each interface as traits.

(My idea was really a hacked together Haskell typeclasses thing where it was knowable at compile time what objects had what behaviours. The other parts of the design are a bit more random.)

@rafaqz
Copy link
Owner

rafaqz commented May 30, 2023

@stemann (now I remember, lol) having Duck() in the @interface macro was legacy from when I used to run test automatically in precompilation.

I really liked the idea of having it all automated so the interface was more reliable. But people didn't like the potential overheads of that approach, so it was dropped and now you have to run test manually.

@stemann
Copy link
Contributor Author

stemann commented May 30, 2023

@stemann (now I remember, lol) having Duck() in the @interface macro was legacy from when I used to run test automatically in precompilation.

I really liked the idea of having it all automated so the interface was more reliable. But people didn't like the potential overheads of that approach, so it was dropped and now you have to run test manually.

Ah, yes - so this PR actually supports the change you made in #8.

While digging around, I also noticed your reasoning in lorenzoh/Invariants.jl#2 (comment)

@stemann
Copy link
Contributor Author

stemann commented May 30, 2023

I really havent read it as closely as you, I meant on a really surface level scan of the code.

That syntax does look nice though... but do you mean it cant actually check return values?

I thought the main difference overall was that my version gives other packages a breakdown of the implementation of each interface as traits.

(My idea was really a hacked together Haskell typeclasses thing where it was knowable at compile time what objects had what behaviours. The other parts of the design are a bit more random.)

Yeah, I think that that syntax also merits some consideration.

It's been a while since I looked at https://github.com/quinnj/Interfaces.jl - but I would think that it can check what is specified in the interface - that methods exist and with the proper signatures - including return value types, but not return values, no. Probably worth getting back to in #9 (or a follow-up - to get #9 closed).

In my view, your version provides a much more flexible interface specification - covering all of:

  1. An empty interface specification, e.g. just defining that AnimalInterface is implemented wrt. Ducks - with no mandatory predicates.
  2. A signature interface specification, where mandatory predicates check that methods exist and return the proper types.
  3. A detailed interface specification, where mandatory predicates check that methods not only exist and return the proper types, but also check various interactions among methods, e.g. to support defining interface behaviour instead of explaining it as in overlapping role of camera constructor and 'open!' stemann/Cameras.jl#7 (comment)
  4. An interface specification with optional parts

... though I'm still not completely clear if interfaces with optional parts is better than separating optional parts into separate interface specifications (cf. #9).

@rafaqz
Copy link
Owner

rafaqz commented May 30, 2023

I think the last point will need to be resolved by implementing a bunch of interfaces for base and package types.

There are often kind of irrelevent parts of an interface that are nevertheless still part of it. For example a DimensionalData.jl AbstractDimArray can keep dims to track what slice an array came from, or disguard them. Theyre pretty much irrelevant, but its nice to see in plot labels. And if you do implement it, you want the plot labels to be right.

It felt really clunky to me define an interface spec for these tiny things separately. My hunch is that people wont do it unless it also feels trivial.

@quinnj
Copy link

quinnj commented Jun 1, 2023

Note there is also https://github.com/Keno/InterfaceSpecs.jl with similar ideas

@rafaqz
Copy link
Owner

rafaqz commented Jun 1, 2023

Also with what looks like proof solver built in 😂

Nice to have that "ok there are smarter people working on this now" feeling

@stemann
Copy link
Contributor Author

stemann commented Jun 3, 2023

Also with what looks like proof solver built in 😂

Nice to have that "ok there are smarter people working on this now" feeling

Indeed! 😃 I think I’ve finally grasped what Keno is saying - after the third read or so :-)

But it might be worthy of a separate issue to consider the suggestion of being open to extension with proof engines.

… and the forall 👀

IIUC Keno shared his thoughts to “aid the direction” of, e.g. these efforts.

@stemann stemann deleted the feature/move_test_objects branch August 9, 2023 08:52
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.

4 participants