# Specialization on vararg of types

**URL:** <https://discourse.julialang.org/t/specialization-on-vararg-of-types/108251>\
**Category:** General Usage\
**Tags:** question\
**Created:** [January 2, 2024, 9:18am UTC](https://discourse.julialang.org/t/specialization-on-vararg-of-types/108251 "2024-01-02T09:18:41Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [January 2, 2024, 9:18am UTC](https://discourse.julialang.org/t/specialization-on-vararg-of-types/108251/1 "2024-01-02T09:18:41Z")

</div>

How do I get Julia to specialize to `Vararg{Type}`?

The [docs](https://docs.julialang.org/en/v1/manual/performance-tips/#Be-aware-of-when-Julia-avoids-specializing) say:

> As a heuristic, Julia avoids automatically [specializing](https://docs.julialang.org/en/v1/manual/methods/#man-method-specializations) on argument type parameters in three specific cases: `Type`, `Function`, and `Vararg`.

However, they say you can get around this for `Vararg`, for arbitrary types with `Vararg{Any,N} where N`:

```julia
julia> h_vararg(x::Vararg{Any, N}) where {N} = x
h_vararg (generic function with 1 method)

julia> Base.specializations(@which h_vararg(1f0, 1.0))
Base.MethodSpecializations(MethodInstance for h_vararg(::Float32, ::Float64))

```

Then they say you can specialize on `Type` with `Type{T} where T`:

```julia
julia> g_type(t::Type{T}) where T = t
g_type (generic function with 1 method)

julia> Base.specializations(@which g_type(Float32))
Base.MethodSpecializations(MethodInstance for g_type(::Type{Float32}))

```

But what about if I want to combine them? Let’s see:

```julia
julia> h2_vararg(x::Vararg{Any,N}) where {N} = x
h2_vararg (generic function with 1 method)

julia> Base.specializations(@which h2_vararg(Float32, Float64))
Base.MethodSpecializations(MethodInstance for h2_vararg(::Type, ::Type))

```

Nope, this doesn’t specialize because we didn’t do the `Type{T} where T` trick. But how should we do that within a `Vararg`? Maybe just this?

```julia
julia> h3_vararg(x::Vararg{Type,N}) where {N} = x
h3_vararg (generic function with 1 method)

julia> Base.specializations(@which h3_vararg(Float32, Float64))
Base.MethodSpecializations(MethodInstance for h3_vararg(::Type, ::Type))

```

Nope… So how do we actually force specialization here?

* * *

Related:

- This came up in some of the discussion on [Proposed alias for union types](https://discourse.julialang.org/t/proposed-syntax-for-building-union-types/108205). While I think for that particular case it wouldn’t make sense because you would blow up the number of methods, I was just curious how you would actually do this if you wanted to.
- Also: [Force specialization on varargs when the arguments are not all of the same concrete type](https://discourse.julialang.org/t/force-specialization-on-varargs-when-the-arguments-are-not-all-of-the-same-concrete-type/34788) by @dilumaluthge

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [January 2, 2024, 9:32am UTC](https://discourse.julialang.org/t/specialization-on-vararg-of-types/108251/2 "2024-01-02T09:32:10Z")

</div>

Found a way!

```julia
julia> @generated f_vararg(t::Vararg{Type}) = :(t)
f_vararg (generic function with 1 method)

julia> Base.specializations(@which f_vararg(Float32, Float64))
Base.MethodSpecializations(MethodInstance for f_vararg(::Type{Float32}, ::Type{Float64}))

```

Not sure if there’s a way that avoids generated functions, but this at least gets the job done.

But please share any alternatives as I am quite curious!

---

<div class="post-metadata">

**Author:** ![danielwe](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielwe/32/35657_2.png) [@danielwe](https://discourse.julialang.org/u/danielwe)\
**Post date:** [January 2, 2024, 11:14am UTC](https://discourse.julialang.org/t/specialization-on-vararg-of-types/108251/3 "2024-01-02T11:14:13Z")

</div>

```julia-repl
julia> f_vararg(t::Vararg{Type{<:T}}) where {T} = t
f_vararg (generic function with 1 method)

julia> Base.specializations(@which f_vararg(Float32, Float64))
Base.MethodSpecializations(MethodInstance for f_vararg(::Type{Float32}, ::Type{Float64}))

```

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [January 2, 2024, 12:44pm UTC](https://discourse.julialang.org/t/specialization-on-vararg-of-types/108251/4 "2024-01-02T12:44:06Z")

</div>

Brilliant!

Do you have some intuition for why this works? For instance, I would have thought you would have to declare the `N` number for the `Vararg` to specialize. But it seems here it is not needed.

---

<div class="post-metadata">

**Author:** ![danielwe](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/danielwe/32/35657_2.png) [@danielwe](https://discourse.julialang.org/u/danielwe)\
**Post date:** [January 2, 2024, 12:52pm UTC](https://discourse.julialang.org/t/specialization-on-vararg-of-types/108251/5 "2024-01-02T12:52:42Z")

</div>

No idea. Just poked around to find something that worked. But I’m guessing that having a `Type` that depends on a declared typevar (i.e., a `where` clause) somewhere in the signature is sufficient to force specialization.

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [January 2, 2024, 12:54pm UTC](https://discourse.julialang.org/t/specialization-on-vararg-of-types/108251/6 "2024-01-02T12:54:39Z")

</div>

Just be aware of a kinda-weird interplay between types and parameterizations here:

```julia
julia> f_vararg(t::Vararg{Type{<:T}}) where {T} = T

# ok
julia> f_vararg(Int, Int)
Int64

# specializes and generally works, but T is not defined
julia> f_vararg(Int, String)
ERROR: UndefVarError: `T` not defined

```

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [January 2, 2024, 1:05pm UTC](https://discourse.julialang.org/t/specialization-on-vararg-of-types/108251/7 "2024-01-02T13:05:42Z")

</div>

> [@MilesCranmer](#):
>
> However, they say you can get around this for `Vararg`, for arbitrary types with `Vararg{Any,N} where N`:

I don’t know if it was left out for some good reason, but the `Vararg` section is actually pretty vague about what is being specialized. Unlike lone functions and types, `Vararg` specifies multiple types with a possibly fixed arity. From what I could tell (though you should probably verify yourself), the “nonspecializing” `__(x::Int...)` specializes on `Int` just fine, it’s the arity it gives up on. Even `__(x...)` specializes on the first argument’s type in 2 specializations, one where the rest of the types match, and one where they are `Any` (or whatever abstract type annotates x). Making a method parameter `__(x::T...) where T` specializes on arguments matching 1 concrete type (and does not specialize on arity); this also specializes on input types like `Type{Float32}` already. Specifying the arity parameter, usually `N`, specializes over arity and every argument’s type.

That is, _except_ for types themselves as inputs, which I still don’t understand. If consistent with non-type inputs, specializations should show their concrete types `DataType` (most types), `Union` (unions), `UnionAll` (iterated unions of parametric types), not the abstract `Type`. You say it doesn’t specialize, and for good reason, but it’s certainly not falling back to `Any` for the 2nd argument onward, either. It’s more like it specializes, but weirdly. `Vararg{Type,N}` doesn’t change anything because there’s no method parameter for type selection.

danielwe’s solution performs the type selection pattern for unlimited arguments in an unintuitive and creative way. It can be written fully as `f_vararg(t::Vararg{Type{S} where S<:T}) where {T}`, so it makes sense that each selected type `S` is a subtype of the 1 method parameter `T<:Any`. This also specializes over arity despite not specifying a method parameter for it, so this is indeed the equivalent to `h_vararg(x::Vararg{Any, N}) where {N}` for non-type inputs.

Other equivalents for completion:

- `__(t::Vararg{Type{Int}})` specifies a type but does not specialize on arity, like `__(x::Int...)` for non-type inputs.
- no equivalents to `__(x::T...) where T` or `__(x...)` because a lone method parameter for the type also specializes on arity. Weird how `x::T...` didn’t specialize on arity too, but maybe the syntax mattered.
- `__(t::Vararg{Type{Int}, N}) where N` specializes on arity and the specified type, like `__(x::Vararg{Int, N}) where N` for non-type inputs.
- `__(t::Vararg{Type{T}}) where {T}` specializes on arity and 1 matching type, like `__(x::Vararg{T,N}) where {T,N}`, which also handles input types already.

> [@aplavin](#):
>
> `# specializes and generally works, but T is not defined`

In the vast majority of cases, a method parameter must be 1 known instance at compile-time for dispatch to work, even if it is absent from the body. I suppose an exception was spotted finally. Example below of the usual case:

```julia-auto
julia> myeltype(::Type{S} where {S<:AbstractArray{T,N} where N}) where {T} = 42
myeltype (generic function with 1 method)

julia> myeltype(Union{Vector{Int}, Matrix{Int}}) # T is Int
42

julia> myeltype(Union{Vector{Int}, Vector{Bool}}) # T is unspecific
ERROR: MethodError: no method matching myeltype(::Type{Union{Vector{Bool}, Vector{Int64}}})

```

For posterity since this sort of compiler stuff might be version-dependent, this was all done on v1.10.0

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [January 2, 2024, 1:54pm UTC](https://discourse.julialang.org/t/specialization-on-vararg-of-types/108251/8 "2024-01-02T13:54:36Z")

</div>

> [@danielwe](#):
>
> `f_vararg(t::Vararg{Type{<:T}}) where {T} = t`

NB: `Test.detect_unbound_args` warns about this method because the static parameter is not always fully constrained (as shown by @aplavin):

```julia-repl
julia> f_vararg(t::Vararg{Type{<:T}}) where {T} = t
f_vararg (generic function with 1 method)

julia> using Test

julia> detect_unbound_args(Main)
[1] f_vararg(t::Type{<:T}...) where T @ Main REPL[1]:1

julia> @code_warntype f_vararg(Int, String)
MethodInstance for f_vararg(::Type{Int64}, ::Type{String})
  from f_vararg(t::Type{<:T}...) where T @ Main REPL[1]:1
Static Parameters
  T <: Union{Int64, String}
[...]

```

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [January 2, 2024, 1:55pm UTC](https://discourse.julialang.org/t/specialization-on-vararg-of-types/108251/9 "2024-01-02T13:55:53Z")

</div>

> [@aplavin](#):
>
> ```julia-auto
> julia> f_vararg(Int, String)
> ERROR: UndefVarError: `T` not defined
> 
> ```

NB: JET.jl is buggy here, reporting a false negative:

> <https://github.com/aviatesk/JET.jl/issues/586>
>
> \`@report\_call\` says that "no errors" are detected, but the call deterministicall…y throws \`UndefVarError\`.
> 
> \`\`\`julia-repl
> julia\> f\_vararg(t::Vararg{Type{\<:T}}) where {T} = T
> f\_vararg (generic function with 1 method)
> 
> julia\> using JET
> 
> julia\> @report\_call f\_vararg(Int, String)
> No errors detected
> 
> 
> julia\> @report\_opt f\_vararg(Int, String)
> No errors detected
> 
> 
> julia\> f\_vararg(Int, String)
> ERROR: UndefVarError: \`T\` not defined in static parameter matching
> Suggestion: run Test.detect\_unbound\_args to detect method arguments that do not fully constrain a type parameter.
> Stacktrace:
> \[1\] f\_vararg(::Type{Int64}, ::Vararg{Type{\<:Union{Int64, String}}})
> @ Main ./REPL\[1\]:1
> \[2\] top-level scope
> @ REPL\[5\]:1
> 
> julia\> versioninfo()
> Julia Version 1.11.0-DEV.1167
> Commit e8f89682d7\* (2023-12-29 05:33 UTC)
> Platform Info:
> OS: Linux (x86\_64-pc-linux-gnu)
> CPU: 8 × AMD Ryzen 3 5300U with Radeon Graphics
> WORD\_SIZE: 64
> LLVM: libLLVM-15.0.7 (ORCJIT, znver2)
> Threads: 1 default, 0 interactive, 1 GC (on 8 virtual cores)
> Environment:
> JULIA\_NUM\_PRECOMPILE\_TASKS = 3
> JULIA\_PKG\_PRECOMPILE\_AUTO = 0
> 
> (@v1.11) pkg\> st JET
> Status \`~/.julia/environments/v1.11/Project.toml\`
> \[c3a54625\] JET v0.8.22
> \`\`\`
> 
> Maybe the fact that the \`f\_vararg(Int, String)\` throws could be a Julia bug, but JET is supposed to flag it anyway, I hope.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [January 2, 2024, 2:07pm UTC](https://discourse.julialang.org/t/specialization-on-vararg-of-types/108251/10 "2024-01-02T14:07:49Z")

</div>

> [@nsajko](#):
>
> `Test.detect_unbound_args` warns about this method because the static parameter is not always fully constrained

Is it possible that this unorthodox feature is also a bug because of this? Would be a shame considering we don’t have alternatives this deep into the type system.

---

<div class="post-metadata">

**Author:** ![penelopeysm](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/penelopeysm/32/213172_2.png) [@penelopeysm](https://discourse.julialang.org/u/penelopeysm)\
**Post date:** [March 4, 2026, 5:05pm UTC](https://discourse.julialang.org/t/specialization-on-vararg-of-types/108251/11 "2026-03-04T17:05:18Z")

</div>

Ran into a very similar issue on Mooncake today: [Severe perf degradations with functions as arguments · Issue #1020 · chalk-lab/Mooncake.jl · GitHub](https://github.com/chalk-lab/Mooncake.jl/issues/1020)

The issue is that it defines `value_and_gradient!!(..., f::F, xs::Vararg{Any,N})` and the `N` does _usually_ cause specialisation … but not when any of `xs` are functions or types. And unfortunately we can’t make the `Any` more specific like above here because `xs` could be literally anything.

AFAIK, the only way to fix it is to

- use a generated function, or
- mark `value_and_gradient!!` with `@inline`, which I think bypasses the need for a specialisation, or
- manually define one method `value_and_gradent!!(..., f::F, x1::X1)` per arity, up to a maximum limit above which we don’t care (I notice this is what [GitHub - MasonProtter/SpecializeVarargs.jl: Force specialization for varadic arguments in julia · GitHub](https://github.com/MasonProtter/SpecializeVarargs.jl) does)
