Varargs and type aliasing

I tried to create a type alias of Vararg (with a specific number of arguments) as follows.

const FormArgs{K,D} = Vararg{StaticVector{D}, K}

However, if I try FormArgs{3,2} it does not produce Vararg{StaticVector{2},3} and an error is thrown. I think that is because Vararg is not really a DataType. Thus, its behavior differs from the similar

const FormTuple{K,D} = NTuple{K, StaticVector{D}}

which works as expected.

However, if you try to use

f(args::FormTuple{K,D}) where {K,D} = ... # function definition using args[1] ... args[K]

you are forced to pass arguments to f wrapped in a tuple. In practice, I think you can work around the issue with a helper method

f(args...) = f(args)

but I am wondering if there is a less hacky approach.


Edit: Using f(args...) = f(args) as above will result in infinite recursion of f into itself if the wrong number of arguments is passed. Better not do that.

In practice, I am using this in a callable type that is parameterized by a known number of arguments, so the pattern is OK:

(::MyCallable{K})(x::FormTuple{K,D}}) where {K,D} = ...
(instance::MyCallable{K})(x::Vararg{Any,K}) where {K} = instance(x)  # defer to method above

A MethodError is thrown if the wrong number of arguments are used.

Is

using StaticArrays
@show StaticVector{3,Int} SVector{3,Int}(1,2,3)
const FormArgs{K,D} = Tuple{Vararg{StaticVector{D},K}}
@show FormArgs{3,2} FormArgs{3,2}((SVector{2}(1,2), SVector{2}(3,4), SVector{2}(5,6)))

yielding

StaticVector{3, Int} = StaticVector{3, Int64}
SVector{3, Int}(1, 2, 3) = [1, 2, 3]
FormArgs{3, 2} = Tuple{StaticVector{2}, StaticVector{2}, StaticVector{2}}
FormArgs{3, 2}((SVector{2}(1, 2), SVector{2}(1, 2), SVector{2}(1, 2))) = ([1, 2], [1, 2], [1, 2])

what you are after? AFAIU Vararg should (always?) be used in conjunction with Tuple?

Thanks for the response. Let me clarify a little.

I was functionally trying to construct a “method signature” alias as a convenient shorthand

const FormArgs{K,D} = Vararg{StaticVector{D}, K}
f(vs::FormArgs{K,D}) where {K,D} = ...
g(vs::FormArgs{K,D}) where {K,D} = ...
# bunch of other functions with similar methods

which I expected to be equivalent to writing

f(vs::Vararg{StaticVector{D}, K}) where {K,D} = ...

But using Vararg without Tuple like that doesn’t work as you pointed out.

What does work is

const FormArgs{K,D} = Tuple{Vararg{StaticVector{D}, K}}
f(vs::FormArgs{K,D}) where {K,D} = ...

but that requires you to pass arguments like f((v1, v2, v3)) rather than f(v1, v2, v3), and I want the latter.

I also erroneously tried

const FormArgs{K,D} = Tuple{Vararg{StaticVector{D}, K}}
f(::FormArgs{K,D}...) where {K,D} = ...

but that method expects zero or more FormArgs{K,D} <: Tuples as arguments rather than exactly K of StaticVector{D}'s.

Not sure where the problem is. The following works for me:

julia> const FormArgs{K,D} = Vararg{StaticVector{D}, K};

julia> blah(x::FormArgs{D,K}) where {D, K} = (D, K);

julia> s = @SVector [1];

julia> blah(s)
(1, 1)

julia> blah(s,s)
(2, 1)

julia> blah(s,s,s)
(3, 1)

That is interesting. When I try, I get the following error.

julia> const FormArgs{K,D} = Vararg{StaticVector{D},K}
Vararg{StaticVector}

julia> blah(x::FormArgs{K,D}) where {K,D} = (K, D)
ERROR: ArgumentError: apply_type: too many arguments (expected 2)

Also,

julia> FormArgs{2}
Vararg{StaticVector, 2}

julia> FormArgs{2,3}
ERROR: ArgumentError: apply_type: too many arguments (expected 2)

julia> const FormTuple{K,D} = Tuple{Vararg{StaticVector{D},K}}
Tuple{Vararg{StaticVector{D}, K}} where {K, D}

julia> FormTuple{2,3}   # works as expected
Tuple{StaticVector{3}, StaticVector{3}}
1 Like

What julia version are you using? I was on 1.6 above.


Edit:
Yes, this fails in 1.7. Now I see what you’re seeing:

julia> const FormArgs{T, K} = Vararg{T, K}  # Note it has nothing to do with StaticArrays
Vararg{Any}

julia> FormArgs{Int, 2}
ERROR: ArgumentError: apply_type: too many arguments (expected 2)
Stacktrace:
 [1] top-level scope
   @ REPL[3]:1

Oh interesting. As you might have guessed, I am using Julia 1.7.

I do not see anything on Vararg in the release notes.
https://docs.julialang.org/en/v1/NEWS/

Strangely, this seems to only apply to Vararg.

julia> Vararg{T, N} where {T, N}
Vararg{Any}

julia> Array{T, N} where {T, N}
Array

julia> NTuple{N, T} where {N, T}
Tuple{Vararg{T, N}} where {N, T} # this is the same as in older versions; no problem here.

Julia 1.6 and below:

julia> Vararg{T, N} where {T, N}
Vararg

Whatever caused this change isn’t in the 1.7 release notes as far as I can tell. It’s worth opening a a github issue about it.

To get around it, you can still use the NTuple version. You just need to create a method for non-NTuple Tuples to avoid the infinite recursion:

julia> const FormArgs{D, K} = NTuple{K, Vector{D}};

julia> f(x::FormArgs{D, K}) where {D, K} = (D, K);

julia> f(x...) = f(x);

julia> f(x::Tuple) = error("non-homogenous tuple");

julia> f([1])
(Int64, 1)

julia> f([1], [1])
(Int64, 2)

julia> f([1], [1.0])
ERROR: non-homogenous tuple
# <...>

This will work with your case just as well. The issue is with how aliasing Vararg is working, not with the specific types that are in it.

1 Like

Clearly a regression then. You might file an issue?

Edit: issue filed.

1 Like

Thanks! I was about to do the same and then saw that you beat me to it.

1 Like