# Type instability with (fairly) simple Tuples

**URL:** <https://discourse.julialang.org/t/type-instability-with-fairly-simple-tuples/133304>\
**Category:** Performance\
**Tags:** tuple, type-stability, ntuple\
**Created:** [October 20, 2025, 11:26am UTC](https://discourse.julialang.org/t/type-instability-with-fairly-simple-tuples/133304 "2025-10-20T11:26:52Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![johnomotani](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/johnomotani/32/26753_2.png) [@johnomotani](https://discourse.julialang.org/u/johnomotani)\
**Post date:** [October 20, 2025, 11:26am UTC](https://discourse.julialang.org/t/type-instability-with-fairly-simple-tuples/133304/1 "2025-10-20T11:26:52Z")

</div>

Should I expect the following to be type-stable? I think the types are inferrable in principle, but maybe the Tuple-of-Pairs structure is just too ‘complicated’?

```julia
julia> using Cthulhu

julia> function foo(t::Pair{Symbol,Int64}...)
       t1 = Tuple(x[1] for x in t)
       t2 = Tuple(x[2] for x in t)
       return nothing
       end
foo (generic function with 1 method)

julia> @descend foo(:a=>1, :b=>2, :c=>3)
foo(t::Pair{Symbol, Int64}...) @ Main REPL[25]:1
1 function foo(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}}::Pair{Symbol,Int64}...)::Core.Const(nothing)
2 t1::Tuple{Vararg{Symbol}} = Tuple(x[1] for x in t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}}::Base.Generator{Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}}, var"#foo##12#foo##13"})
3 t2::Tuple{Vararg{Int64}} = Tuple(x[2] for x in t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}}::Base.Generator{Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}}, var"#foo##14#foo##15"})
4 return nothing::Core.Const(nothing)
5 end
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [w]arn, [h]ide type-stable statements, [t]ype annotations, [s]yntax highlight for Source/LLVM/Native, [j]ump to source always.
Show: [S]ource code, [A]ST, [T]yped code, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
 • x in t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}}
   t1
    x in t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}}
   t2
   ↩

```

I tried several variations, e.g. making `t` an `NTuple` rather than splatting, but didn’t find any that made any difference.

This is an issue for me because for example trying to use `t2` to create an array `a = zeros(t2...)` will not know the dimension of the array, introducing more type instability. Going to try to work around this somehow, but suspect alternatives will make my code a little bit less readable…

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [October 20, 2025, 11:44am UTC](https://discourse.julialang.org/t/type-instability-with-fairly-simple-tuples/133304/2 "2025-10-20T11:44:08Z")

</div>

I think the problem is that using comprehension syntax lowers to `Base.Generator` which doesn’t have the length in the type. So you’d need the length to be constant-propagated by the compiler, which I guess it isn’t.

Anyway the answer here is to use `map(x->x[1], t)` or `ntuple(i->t[i][1], length(t))`. These should be type stable.

---

<div class="post-metadata">

**Author:** ![johnomotani](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/johnomotani/32/26753_2.png) [@johnomotani](https://discourse.julialang.org/u/johnomotani)\
**Post date:** [October 20, 2025, 11:58am UTC](https://discourse.julialang.org/t/type-instability-with-fairly-simple-tuples/133304/3 "2025-10-20T11:58:35Z")

</div>

Thanks @cjdoris.

Using `map` works!

```julia
julia> function foo_map(t::Pair{Symbol,Int64}...)
       t1 = map(x->x[1], t)
       t2 = map(x->x[2], t)
       return nothing
       end
foo_map (generic function with 1 method)

julia> @descend foo_map(:a=>1, :b=>2, :c=>3)
foo_map(t::Pair{Symbol, Int64}...) @ Main REPL[35]:1
1 function foo_map(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}}::Pair{Symbol,Int64}...)::Core.Const(nothing)
2 t1::Tuple{Symbol, Symbol, Symbol} = map(x->x[1], t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})::Tuple{Symbol, Symbol, Symbol}
3 t2::Tuple{Int64, Int64, Int64} = map(x->x[2], t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})::Tuple{Int64, Int64, Int64}
4 return nothing::Core.Const(nothing)
5 end
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [w]arn, [h]ide type-stable statements, [t]ype annotations, [s]yntax highlight for Source/LLVM/Native, [j]ump to source always.
Show: [S]ource code, [A]ST, [T]yped code, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
 • map(x->x[1], t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})
   map(x->x[2], t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})
   ↩

```

However, using `NTuple` seems not to help.

```julia
julia> function foo_NTuple(t::Pair{Symbol,Int64}...)
       t1 = NTuple(i->t[i][1], length(t))
       t2 = NTuple(i->t[i][2], length(t))
       return nothing
       end
foo_NTuple (generic function with 1 method)

julia> @descend foo_NTuple(:a=>1, :b=>2, :c=>3)
foo_NTuple(t::Pair{Symbol, Int64}...) @ Main REPL[37]:1
1 function foo_NTuple(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}}::Pair{Symbol,Int64}...)::Union{}
2 t1::Union{} = NTuple::Type{NTuple{N, T} where {N, T}}(i->t[i][1], length(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})::Core.Const(3))::Union{}
3 t2 = NTuple(i->t[i][2], length(t))
4 return nothing
5 end
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [w]arn, [h]ide type-stable statements, [t]ype annotations, [s]yntax highlight for Source/LLVM/Native, [j]ump to source always.
Show: [S]ource code, [A]ST, [T]yped code, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
 • length(t)
   length(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})
   ↩

```

and even if I make the length a type parameter, and use `first()` and `second()` instead of indexing

```julia
julia> function foo_NTuple2(t::Vararg{Pair{Symbol,Int64},N}) where N
       t1 = NTuple(i->first(t[i]), Val(N))
       t2 = NTuple(i->second(t[i]), Val(N))
       return nothing
       end
foo_NTuple2 (generic function with 1 method)

julia> @descend foo_NTuple2(:a=>1, :b=>2, :c=>3)
foo_NTuple2(t::Vararg{Pair{Symbol, Int64}, N}) where N @ Main REPL[43]:1
1 function (foo_NTuple2(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}}::Vararg{Pair{Symbol,Int64},N}) where N)::Union{}
2 t1::Union{} = NTuple::Type{NTuple{N, T} where {N, T}}(i->first(t[i]), Val(N::Core.Const(3))::Core.Const(Val{3}()))::Union{}
3 t2 = NTuple(i->second(t[i]), Val(N))
4 return nothing
5 end
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [w]arn, [h]ide type-stable statements, [t]ype annotations, [s]yntax highlight for Source/LLVM/Native, [j]ump to source always.
Show: [S]ource code, [A]ST, [T]yped code, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
 • %3 = _typeof_captured_variable(::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})::Core.Const(Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})
   Val(N::Core.Const(3))
   ↩

```

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [October 20, 2025, 12:04pm UTC](https://discourse.julialang.org/t/type-instability-with-fairly-simple-tuples/133304/4 "2025-10-20T12:04:22Z")

</div>

You can try `ntuple`:

```julia-auto
function foo_ntuple(t::Vararg{Pair{Symbol,Int64},N}) where N
    t1 = ntuple(i->t[i][1], Val(N))
    t2 = ntuple(i->t[i][2], Val(N))
    return nothing
end
```

---

<div class="post-metadata">

**Author:** ![cjdoris](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cjdoris/32/213133_2.png) [@cjdoris](https://discourse.julialang.org/u/cjdoris)\
**Post date:** [October 20, 2025, 12:12pm UTC](https://discourse.julialang.org/t/type-instability-with-fairly-simple-tuples/133304/5 "2025-10-20T12:12:10Z")

</div>

Oh yes `ntuple` not `NTuple`!

---

<div class="post-metadata">

**Author:** ![johnomotani](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/johnomotani/32/26753_2.png) [@johnomotani](https://discourse.julialang.org/u/johnomotani)\
**Post date:** [October 20, 2025, 12:13pm UTC](https://discourse.julialang.org/t/type-instability-with-fairly-simple-tuples/133304/6 "2025-10-20T12:13:33Z")

</div>

~~Interesting - `ntuple` does better, but seems to fail to get the type of the second entry (even though `map` was able to!)~~ Actually using `second` was the problem here, `ntuple` is fine - see next post.

```julia
julia> function foo_ntuple(t::Vararg{Pair{Symbol,Int64},N}) where N
       t1 = ntuple(i->first(t[i]), Val(N))
       t2 = ntuple(i->second(t[i]), Val(N))
       return nothing
       end
foo_ntuple (generic function with 1 method)

julia> @descend foo_ntuple(:a=>1, :b=>2, :c=>3)
foo_ntuple(t::Vararg{Pair{Symbol, Int64}, N}) where N @ Main REPL[45]:1
1 function (foo_ntuple(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}}::Vararg{Pair{Symbol,Int64},N}) where N)::Core.Const(nothing)
2 t1::Tuple{Symbol, Symbol, Symbol} = ntuple(i->first(t[i]), Val(N::Core.Const(3))::Core.Const(Val{3}()))::Tuple{Symbol, Symbol, Symbol}
3 t2::Tuple{Any, Any, Any} = ntuple(i->second(t[i]), Val(N::Core.Const(3))::Core.Const(Val{3}()))::Tuple{Any, Any, Any}
4 return nothing::Core.Const(nothing)
5 end
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [w]arn, [h]ide type-stable statements, [t]ype annotations, [s]yntax highlight for Source/LLVM/Native, [j]ump to source always.
Show: [S]ource code, [A]ST, [T]yped code, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
 • %3 = _typeof_captured_variable(::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})::Core.Const(Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})
   Val(N::Core.Const(3))
   ntuple(i->first(t[i]), Val(N::Core.Const(3))::Core.Const(Val{3}()))
   %13 = _typeof_captured_variable(::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})::Core.Const(Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})
   Val(N::Core.Const(3))
   ntuple(i->second(t[i]), Val(N::Core.Const(3))::Core.Const(Val{3}()))
   ↩

```

I think `map` may be my new best friend for working with Tuples 🙂

---

<div class="post-metadata">

**Author:** ![johnomotani](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/johnomotani/32/26753_2.png) [@johnomotani](https://discourse.julialang.org/u/johnomotani)\
**Post date:** [October 20, 2025, 12:21pm UTC](https://discourse.julialang.org/t/type-instability-with-fairly-simple-tuples/133304/7 "2025-10-20T12:21:58Z")

</div>

Looks like `second()` was the problem, going back to indexing the `Pair`s works

```julia
julia> function foo_ntuple2(t::Vararg{Pair{Symbol,Int64},N}) where N
       t1 = ntuple(i->t[i][1], Val(N))
       t2 = ntuple(i->t[i][2], Val(N))
       return nothing
       end
foo_ntuple2 (generic function with 1 method)

julia> @descend foo_ntuple2(:a=>1, :b=>2, :c=>3)
foo_ntuple2(t::Vararg{Pair{Symbol, Int64}, N}) where N @ Main REPL[53]:1
1 function (foo_ntuple2(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}}::Vararg{Pair{Symbol,Int64},N}) where N)::Core.Const(nothing)
2 t1::Tuple{Symbol, Symbol, Symbol} = ntuple(i->t[i][1], Val(N::Core.Const(3))::Core.Const(Val{3}()))::Tuple{Symbol, Symbol, Symbol}
3 t2::Tuple{Int64, Int64, Int64} = ntuple(i->t[i][2], Val(N::Core.Const(3))::Core.Const(Val{3}()))::Tuple{Int64, Int64, Int64}
4 return nothing::Core.Const(nothing)
5 end
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [w]arn, [h]ide type-stable statements, [t]ype annotations, [s]yntax highlight for Source/LLVM/Native, [j]ump to source always.
Show: [S]ource code, [A]ST, [T]yped code, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
 • %3 = _typeof_captured_variable(::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})::Core.Const(Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})
   Val(N::Core.Const(3))
   ntuple(i->t[i][1], Val(N::Core.Const(3))::Core.Const(Val{3}()))
   %13 = _typeof_captured_variable(::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})::Core.Const(Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})
   Val(N::Core.Const(3))
   ntuple(i->t[i][2], Val(N::Core.Const(3))::Core.Const(Val{3}()))
   ↩

```

---

<div class="post-metadata">

**Author:** ![johnomotani](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/johnomotani/32/26753_2.png) [@johnomotani](https://discourse.julialang.org/u/johnomotani)\
**Post date:** [October 20, 2025, 12:25pm UTC](https://discourse.julialang.org/t/type-instability-with-fairly-simple-tuples/133304/8 "2025-10-20T12:25:03Z")

</div>

Also the `Val(N)` bit was unnecessary

```julia
julia> function foo_ntuple3(t::Pair{Symbol,Int64}...)
       t1 = ntuple(i->t[i][1], length(t))
       t2 = ntuple(i->t[i][2], length(t))
       return nothing
       end
foo_ntuple3 (generic function with 1 method)

julia> @descend foo_ntuple3(:a=>1, :b=>2, :c=>3)
foo_ntuple3(t::Pair{Symbol, Int64}...) @ Main REPL[55]:1
1 function foo_ntuple3(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}}::Pair{Symbol,Int64}...)::Core.Const(nothing)
2 t1::Tuple{Symbol, Symbol, Symbol} = ntuple(i->t[i][1], length(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})::Core.Const(3))::Tuple{Symbol, Symbol, Symbol}
3 t2::Tuple{Int64, Int64, Int64} = ntuple(i->t[i][2], length(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})::Core.Const(3))::Tuple{Int64, Int64, Int64}
4 return nothing::Core.Const(nothing)
5 end
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [w]arn, [h]ide type-stable statements, [t]ype annotations, [s]yntax highlight for Source/LLVM/Native, [j]ump to source always.
Show: [S]ource code, [A]ST, [T]yped code, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
 • length(t)
   length(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})
   ntuple(i->t[i][1], length(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})::Core.Const(3))
   length(t)
   length(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})
   ntuple(i->t[i][2], length(t::Tuple{Pair{Symbol, Int64}, Pair{Symbol, Int64}, Pair{Symbol, Int64}})::Core.Const(3))
   ↩

```
