So, I made this script where I have a type like this:
struct Foo
v::NTuple{8, Union{String, Nothing}}
end
The reason being that I want the compiler to be able to enforce a length of 8 for me, and also that a an object more lightweight than an array wouldn’t be bad, either.
However, code involving Foo is inherently type unstable because:
Which is due to tuples being co-variant in their type parameters.
Is there any way for me to use tuples - or something like tuples - such that they can be concretely typed, while also containing Union elements?
Here are the approaches I’ve tried
Use an SVector from StaticArrays.jl. Doesn’t work - since StaticArrays implements SVector with tuples, it’s still type unstable
Just use a vector, then check during instantiation that the length of the vector is 8. Doable, but not that nice - for example, the compiler cannot tell me if I accidentally mess up the lengths, and cannot automatically elide bounds checks etc.
Wrap the union in a struct Bar, then have Foo contain NTuple{8, Bar}. This is the solution I ended up going with (using ErrorTypes.jl), and it does work, but I’m wondering if I missed anything simpler.
julia> struct Foo3{T<:NTuple{8,Union{String,Nothing}}}
v::T
end
julia> x = Foo3(ntuple(i -> i < 4 ? "a" : nothing, 8))
Foo3{Tuple{String, String, String, Nothing, Nothing, Nothing, Nothing, Nothing}}(("a", "a", "a", nothing, nothing, nothing, nothing, nothing))
julia> x = Foo3(ntuple(i -> i < 4 ? "a" : nothing, 7))
ERROR: MethodError: no method matching Foo3(::Tuple{String, String, String, Nothing, Nothing, Nothing, Nothing})
Closest candidates are:
Foo3(::T) where T<:NTuple{8, Union{Nothing, String}} at REPL[7]:2
Stacktrace:
[1] top-level scope
@ REPL[9]:1
but that will put quite a price on the compiler, as all objects will have different types, dispatch on that will be probably a mess.
To be truth, I’m not sure this has any advantage over the original proposal, unless you the positions of the nothings and strings are the same on every object at the end, in which case functions could specialize to that exact structure. Otherwise anything operating on these or those objects will end up doing run-time dispatch anyway.
Wouldn’t that happen to anything you do with those tuples at the end? Seems to me that it is hard do not end up with a 2^8 dispatch table somewhere, by using any of these approaches.
No, using the struct approach, the tuple itself is type stable, and the only instability is when the Union{Nothing, String} is accessed, in which case union-splitting will take care of it.