RequiredInterfaces.jl
RequiredInterfaces.jl copied to clipboard
Support return type assertions
@timholy brought up a good point in https://github.com/JuliaLang/julia/issues/49973#issuecomment-1658264889, namely that sometimes return type assertions are useful. This would (optionally) be checked as well when checking for interface conformity. The syntax to use this would be (in the example of AbstractArrays):
@required AbstractArray begin
size(::AbstractArray{T,N})::NTuple{N, Int}
getindex(::AbstractArray{T}, ::Int)::T
end
where the T in getindex would be forced to match. This could potentially be implemented with Base.return_type. Though I do admit not liking relying on inference, there's no way around it in this case.
Hi there, thanks for writing this package, it is really great! We are currently using it to streamline a bigger project we have, and I think it would be nice to be able to check that an implemented type both takes the right kind of arguments and returns something expected. Do you think you will incorporate return type assertions into this package?
I still want to support this, yes, but the checking part for this is, in general, a bit tricky. Consider this type for example:
struct MyVector{T} <: AbstractVector{T}
arr::Vector{T}
end
Base.getindex(mv::MyVector{T}, idx::Int) where T = mv.arr[idx]
Base.size(mv::MyVector) = size(mv.arr)
Evidently, this type implements the AbstractVector interface correctly. However, just checking the return types of getindex gives a (maybe surprising) result:
julia> Base.return_types(getindex, (MyVector, Int))
1-element Vector{Any}:
Any
The return type is Any, because we didn't specify what kinds of elements MyVector holds when querying the return types. This is the correct result - we don't know the element type, so anything can be returned. It does work if we give it some form of concrete type:
julia> Base.return_types(getindex, (MyVector{Union{Int,Float64}}, Int))
1-element Vector{Any}:
Union{Float64, Int64}
but then you run into the issue of figuring out that relationship in the first place; really, what the interface requires is not that getindex(av::AbstractVector{T}, i::Int)::T holds, but that getindex(av::AbstractVector{T}, i::Int)::eltype(av) holds, because there is no clear/established API for the semantics of a type parameter (& how to extract them). They are usually ad-hoc constructions - and in this case, points to eltype being a requirement of the interface as well (which by default takes the first type parameter of AbstractVector and claims that as the element type).
To be clear, I still want to have the implicit return type assertion work - it's just a bit more tricky and definitely requires "breaking apart" types given when checking an interface into constituent parts, potentially picking Any and getting Any back out (which is not really helpful, I'd imagine). Not at all impossible to do, but requires a bit of care.
That makes sense. I appreciate the detailed answer, it shows you are thinking it through carefully :) Still, even the ability to specify the interface like your package already allows is really very cool, and definitely a step in the right direction for the Julia ecosystem!