I have a function f(x::Vector)
. It’s tested on Vectors and seems to do okay. I looked at the code and think it should also work for all AbstractVectors, but I don’t know how to test this. In general, are there any principles for writing tests for abstract types? More particularly in the case of AbstractVectors and AbstractArrays, is there a list of common mistakes (e.g. 1:length(x) indexing) compiled somewhere? And most particularly, this is the function I’m trying to test.
You probably intend a function f(x::AbstractVector)
as a Vector
is not the same thing as an AbstractVector
. (oh, I see that is how you have it in the source code linked).
There are many AbstractVector
types e.g. subtypes(AbstractVector)
.
You are likely interested in testing over some set of element types T, like subtypes(Signed)
, subtypes(AbstractFloat)
, or maybe general Real
or Number
types. On the other hand, you may also want to know your function works with vectors of Char
and vectors of String
– or not. What is your intent?
Yes. I would like to broaden the function definition from f(x::Vector)
to f(x::AbstractVector)
.
All you need to do in Julia is just specify the type (here the set of types) as AbstractVector
and your function will be called with any legit realization of an AbstractVector
. The important part is how to handle such a wide variety of possible inputs in an effective and reasonably concise way.
There some things that make sense for any realization of an AbstractVector, for example, finding its length and determining if the vector is empty. Julia already has length
and isempty
that work with AbstractVectors. We can confirm this using the builtin hasmethod
. To make its use simpler, define
Base.hasmethod(method, ::Type{T}) where {T} = hasmethod(method, (T,))
now
julia> hasmethod(length, AbstractVector)
true
julia> hasmethod(isempty, AbstractVector)
true
You are free to use any of the methods that are predefined for AbstractVector
s in the design of your tests.
Really the only things to consider is OffsetArray
and StaticVector
(if applicable). If those work, pretty much everything else should too.
That’s a nice, easy, answer! I’ll test on those two. What makes you think that those two cover everything?
In this case, I am interested in exploring exotic container subtypes rather than element types. I already plan to narrow the element types to the concrete set Union{Float32, Missing}, Union{Float64, Missing}, Float32, Float64, Missing
This:
https://github.com/JuliaLang/julia/blob/898142d5b59ab75b586d1fbd4d48eec37b004f40/base/sort.jl#L1220
Won’t work for StaticArrays.
Wow! I never would have guessed! Why not?
You cannot overwrite static values.
They are not mutable, you cannot change each component independently. Actually you can’t have a mutating function (indicated there by the !
) written for StaticArrays. The “best” you can do is to redefine them and return the new ones.
So, as a rule of thumb, none of the mutating functions are expected to work on static arrays (although they may provide that possiblity if they return the array as well, depending on what the function does).
Offset arrays cover non-1 based indexing, and static arrays covers the case where you can’t modify the vector. Other than that, the array interface is relatively straightforward. If there’s a bug in something, it would probably be a bug in the array type.
Thanks! I forgot that some of them are immutable in addition to being statically sized. I expect that in this case it is okay to declare a function f!(x::AbstractArray)
and throw a setindex!(...) not defined
error whenever someone calls f!(x::SomeImmutibleSubtypeOfAbstractArray)
?
sure – although that is not strictly necessary … if you don’t , Julia will
julia> using StaticArrays
julia> staticvec = SVector{4}([1,2,3,4])
julia> staticvec[1] = 100
ERROR: setindex!(::SVector{4, Int64}, value, ::Int) is not defined.
This coding style is widely used. When it is important to handle an error or an exception, this style is inappropriate. Otherwise, it works to catch the problem and allows follow up (albeit somewhat in the weeds).
It’s nice that the interface is relatively straightforward. For completeness, this feels like a pretty good reference for implementing AbstractArrays: Interfaces · The Julia Language, is it also the place to look for knowing what guarantees we have about the behavior of abstract arrays, or perhaps I should look at the docstring of each individual function I call?
People wrote that section just for this purpose. You may examine the docstrings of each function as a way to glean deeper understanding – however the point of an interface is to shield us from having to do that.
Yes. Exactly. My intention was to leave the function as is with the knowledge that if someone calls it with a StaticVector
they will get the appropriate result: ERROR: setindex!(...) is not defined
and it is safe in general. This is in stark contrast to the behavior of for i in 2:length(x); @inbounds x[i] = x[i-1]
when passed an OffsetArray
. I want to avoid the latter.