# Type stability while looping over Tuple

**URL:** <https://discourse.julialang.org/t/type-stability-while-looping-over-tuple/83272>\
**Category:** Performance\
**Tags:** type-stability\
**Created:** [June 23, 2022, 7:10pm UTC](https://discourse.julialang.org/t/type-stability-while-looping-over-tuple/83272 "2022-06-23T19:10:20Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![NicolasW](https://avatars.discourse-cdn.com/v4/letter/n/c67d28/32.png) [@NicolasW](https://discourse.julialang.org/u/NicolasW)\
**Post date:** [June 23, 2022, 7:10pm UTC](https://discourse.julialang.org/t/type-stability-while-looping-over-tuple/83272/1 "2022-06-23T19:10:20Z")

</div>

In the performance critical part of our code we encountered a type instability. Is there an easy way to resolve this?

We have some input

```julia
struct Sinput{X,D}
	input::X
end
s_input = Sinput{Tuple{Float64,Int64}, 2}((1.3,4))

```

which gets passed to a function

```julia
function test_for(x::Sinput{X,D}) where {X,D}
	for i=1:D
		x.input[i]^2
	end
end

```

In reality the loop in the function sits inside another loop and calls a function which dispatches on the type of `x.input[i]`. Whats the best way to resolve this and get type-stable code? Why is this not resolved by the compiler, despite knowing the loop size and the type of the input at compile time?

---

<div class="post-metadata">

**Author:** ![raminammour](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/raminammour/32/13572_2.png) [@raminammour](https://discourse.julialang.org/u/raminammour)\
**Post date:** [June 23, 2022, 7:30pm UTC](https://discourse.julialang.org/t/type-stability-while-looping-over-tuple/83272/2 "2022-06-23T19:30:36Z")

</div>

> [@NicolasW](#):
>
> ```julia
> function test_for(x::Sinput{X,D}) where {X,D}
> for i=1:D
> x.input[i]^2
> end
> end
> 
> ```

Here is a possible solution using [Unrolled](https://github.com/cstjean/Unrolled.jl) as long as the tuple is not too long.

```julia
Base.length(::Type{Sinput{X,D}}) where {X,D} = D
@unroll function test_for_unrolled(x::Sinput{X,D}) where {X,D}
                             
                       @unroll for i in 1:length(x)
                                     x.input[i]^2
                             end
                     return nothing
                     end
end
```

---

<div class="post-metadata">

**Author:** ![NicolasW](https://avatars.discourse-cdn.com/v4/letter/n/c67d28/32.png) [@NicolasW](https://discourse.julialang.org/u/NicolasW)\
**Post date:** [June 23, 2022, 7:34pm UTC](https://discourse.julialang.org/t/type-stability-while-looping-over-tuple/83272/3 "2022-06-23T19:34:10Z")

</div>

Unfortunately, in our application the usage of `@unroll` gets blocked by the outer loop and its nesting with the inner loop.

---

<div class="post-metadata">

**Author:** ![raminammour](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/raminammour/32/13572_2.png) [@raminammour](https://discourse.julialang.org/u/raminammour)\
**Post date:** [June 23, 2022, 7:41pm UTC](https://discourse.julialang.org/t/type-stability-while-looping-over-tuple/83272/4 "2022-06-23T19:41:40Z")

</div>

Maybe I am misunderstanding, but why can’t you wrap the inner loop in a function and use `@unroll` on that inner function? Maybe a MWE code closer to the structure you have can help…

Also this [thread](https://discourse.julialang.org/t/multiple-dispatch-with-more-than-4-methods-blocks-type-inference/49066) is useful for a similar discussion.

---

<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:** [June 23, 2022, 8:55pm UTC](https://discourse.julialang.org/t/type-stability-while-looping-over-tuple/83272/5 "2022-06-23T20:55:02Z")

</div>

`map(a -> a^2, x.input)` should be type-stable.

---

<div class="post-metadata">

**Author:** ![NicolasW](https://avatars.discourse-cdn.com/v4/letter/n/c67d28/32.png) [@NicolasW](https://discourse.julialang.org/u/NicolasW)\
**Post date:** [June 23, 2022, 10:16pm UTC](https://discourse.julialang.org/t/type-stability-while-looping-over-tuple/83272/6 "2022-06-23T22:16:40Z")

</div>

Mostly because it results in errors such as `AssertionError: Can only unroll a loop over one of the function's arguments` or `BoundsError: attempt to access 0-element Vector{Any} at index [1]` depending on how I try to break things up.

Here is a more complete example, where it seems to be not possible to trivialize the problem away that easily. Apart from the practical aspect in our code, I’m also interested in the conceptual answer to this question.

Here is the more complete example:

```julia
abstract type AbstractBaseType{T,N} end

mutable struct BaseStruct_1{T, N} <: AbstractBaseType{T,N}
	some_parameter::T
end
mutable struct BaseStruct_2{T, N} <: AbstractBaseType{T,N}
	some_parameter::T
end

Base.length(::Type{BaseStruct_1{T,N}}) where {T,N} = N + 1
Base.length(::Type{BaseStruct_2{T,N}}) where {T,N} = N^2
Base.eltype(::Type{<:AbstractBaseType{T,N}}) where {T,N} = T

struct Sinput{T,X,D}
	input::X
	someArray::Array{T,D}
	buffer::Vector{T}
end

function Sinput(input::X) where {X}
	D = length(input)
	T = eltype(typeof(input[1]))
	dims = Tuple([length(typeof(x)) for x in input])
	someArray = rand(T, dims)
	buffer = ones(T, D)

	Sinput{T,X,D}(input, someArray, buffer)
end

function innercall(el::BaseStruct_1{T,N}, x::T) where {T,N}
	x^2 + el.some_parameter - 3.23
	el.some_parameter += 1.2
end
function innercall(el::BaseStruct_2{T,N}, x::T) where {T,N}
	x - el.some_parameter^2
	el.some_parameter += 1.2
end

function resetInternalParam(el::AbstractBaseType)
	el.some_parameter = 0.
end

function (intP::Sinput{T,X,D})(x::T) where {T,X,D}
	result = zero(T)
	for ind in CartesianIndices(intP.someArray)
		for i=1:D
			if ind[i]==1
				resetInternalParam(intP.input[i])
				intP.buffer[i] = innercall(intP.input[i], x)
			else
				intP.buffer[i] = innercall(intP.input[i], x)
				break
			end
		end
		result += intP.someArray[ind] * prod(intP.buffer)
	end

	return result
end

base_1 = BaseStruct_1{Float64, 5}(1.);
base_2 = BaseStruct_2{Float64, 6}(1.);
s_input = Sinput((base_1, base_2));

s_input(1.2)
@code_warntype s_input(1.2)

```

---

<div class="post-metadata">

**Author:** ![NicolasW](https://avatars.discourse-cdn.com/v4/letter/n/c67d28/32.png) [@NicolasW](https://discourse.julialang.org/u/NicolasW)\
**Post date:** [June 24, 2022, 12:41am UTC](https://discourse.julialang.org/t/type-stability-while-looping-over-tuple/83272/7 "2022-06-24T00:41:59Z")

</div>

Discovering [this thread](https://discourse.julialang.org/t/is-there-a-way-to-forward-getindex-for-tuples-in-a-type-stable-way/2889/2) it can be resolve by recursion

```julia
function RecursiveUnroll(x::Tuple, i::Int)
	if length(x) == 0
		return nothing
	end
	# do what you want here
	RecursiveUnroll(x[2:end], i+1)
end

```

It still seems to me that this should be something the compiler could infer, as the range of the loop is static. Maybe someone could chip in here.
