# Type inference fails for broadcasted addition of tuples containing multiple types

**URL:** <https://discourse.julialang.org/t/type-inference-fails-for-broadcasted-addition-of-tuples-containing-multiple-types/37725>\
**Category:** Performance\
**Created:** [April 17, 2020, 12:04am UTC](https://discourse.julialang.org/t/type-inference-fails-for-broadcasted-addition-of-tuples-containing-multiple-types/37725 "2020-04-17T00:04:11Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![tomohiro\_soejima](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tomohiro_soejima/32/8056_2.png) [@tomohiro\_soejima](https://discourse.julialang.org/u/tomohiro_soejima)\
**Post date:** [April 17, 2020, 12:04am UTC](https://discourse.julialang.org/t/type-inference-fails-for-broadcasted-addition-of-tuples-containing-multiple-types/37725/1 "2020-04-17T00:04:11Z")

</div>

Hi,

I want to write addition function for tuples with unknown length and unknown type e.g. something like `Tuple{a, b, c, d...}` where the length and type is determined at runtime, and a, b, c, and d are custom types with addition defined.

Naively, I would try something like this

```julia
function add_tuple(a, b)
    a .+ b
end

```

This works great for `a :: Tuple{Int, Float64}` :

```julia
@btime add_tuple((1,2.0), (2,3.0))
  0.013 ns (0 allocations: 0 bytes)

@code_warntype add_tuple((1,3.0), (2,2.0))

Variables
  #self#::Core.Compiler.Const(add_tuple, false)
  a::Tuple{Int64,Float64}
  b::Tuple{Int64,Float64}

Body::Tuple{Int64,Float64}
1 ─ %1 = Base.broadcasted(Main.:+, a, b)::Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple},Nothing,typeof(+),Tuple{Tuple{Int64,Float64},Tuple{Int64,Float64}}}
│ %2 = Base.materialize(%1)::Tuple{Int64,Float64}
└── return %2

```

A bit surprisingly, type inference seems to fail for slightly more complicated examples

```julia
struct MyInt
    s :: Int
end

Base.:+(a::MyInt, b::MyInt) = MyInt(a.s + b.s)

a = MyInt(1)
@code_warntype add_tuple((1,a), (2,a))

Variables
  #self#::Core.Compiler.Const(add_tuple, false)
  a::Tuple{Int64,MyInt}
  b::Tuple{Int64,MyInt}

Body::Tuple{Union{Int64, MyInt},Union{Int64, MyInt}}
1 ─ %1 = Base.broadcasted(Main.:+, a, b)::Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple},Nothing,typeof(+),Tuple{Tuple{Int64,MyInt},Tuple{Int64,MyInt}}}
│ %2 = Base.materialize(%1)::Tuple{Union{Int64, MyInt},Union{Int64, MyInt}}
└── return %2

```

Not that type inference fails for `Base.materialize`, even though the compiler should have enough information to figure out the output type. I suspect this has some performance cost in more complicated cases.

My question is why this happens, and if there is anything I can do to force type inference. Thanks!

---

<div class="post-metadata">

**Author:** ![tomohiro\_soejima](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tomohiro_soejima/32/8056_2.png) [@tomohiro\_soejima](https://discourse.julialang.org/u/tomohiro_soejima)\
**Post date:** [April 17, 2020, 12:15am UTC](https://discourse.julialang.org/t/type-inference-fails-for-broadcasted-addition-of-tuples-containing-multiple-types/37725/2 "2020-04-17T00:15:43Z")

</div>

A possibly relevant observation: `getindex` is type unstable for tuple containing multiple types

```julia
a = (1, 1.0)
@code_warntype a[1]

Variables
  #self#::Core.Compiler.Const(getindex, false)
  t::Tuple{Int64,Float64}
  i::Int64

Body::Union{Float64, Int64}
1 ─ nothing
│ %2 = Base.getfield(t, i, $(Expr(:boundscheck)))::Union{Float64, Int64}
└── return %2

```

---

<div class="post-metadata">

**Author:** ![Tamas\_Papp](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tamas_papp/32/25949_2.png) [@Tamas\_Papp](https://discourse.julialang.org/u/Tamas_Papp)\
**Post date:** [April 17, 2020, 5:31am UTC](https://discourse.julialang.org/t/type-inference-fails-for-broadcasted-addition-of-tuples-containing-multiple-types/37725/3 "2020-04-17T05:31:08Z")

</div>

> [@tomohiro\_soejima](#):
>
> `@code_warntype add_tuple((1,a), (2,a))`

Use

```julia
add_tuple(a, b) = map(+, a, b)

```

If you are interested in more complex cases, search for “lispy tuple recursion”.

---

<div class="post-metadata">

**Author:** ![tomohiro\_soejima](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tomohiro_soejima/32/8056_2.png) [@tomohiro\_soejima](https://discourse.julialang.org/u/tomohiro_soejima)\
**Post date:** [April 17, 2020, 6:19am UTC](https://discourse.julialang.org/t/type-inference-fails-for-broadcasted-addition-of-tuples-containing-multiple-types/37725/4 "2020-04-17T06:19:34Z")

</div>

Thanks for the suggestion! I tested your solution

```julia
function add_tuple_map(a, b)
    return map(+, a, b)
end

function add_tuple_broadcast(a, b)
    return broadcast(+, a, b)
end

function add_tuple_dot(a, b)
    return a .+ b
end

a = (1, MyInt(3))
b = (2, MyInt(4))

```

```julia
@code_warntype add_tuple_map(a, b)
Variables
  #self#::Core.Compiler.Const(add_tuple_map, false)
  a::Tuple{Int64,MyInt}
  b::Tuple{Int64,MyInt}

Body::Tuple{Int64,MyInt}
1 ─ %1 = Main.map(Main.:+, a, b)::Tuple{Int64,MyInt}
└── return %1

```

```julia
@code_warntype add_tuple_broadcast(a, b)
Variables
  #self#::Core.Compiler.Const(add_tuple_broadcast, false)
  a::Tuple{Int64,MyInt}
  b::Tuple{Int64,MyInt}

Body::Tuple{Int64,MyInt}
1 ─ %1 = Main.broadcast(Main.:+, a, b)::Tuple{Int64,MyInt}
└── return %1

```

```julia
@code_warntype add_tuple_dot(a, b)
Variables
  #self#::Core.Compiler.Const(add_tuple_dot, false)
  a::Tuple{Int64,MyInt}
  b::Tuple{Int64,MyInt}

Body::Tuple{Union{Int64, MyInt},Union{Int64, MyInt}}
1 ─ %1 = Base.broadcasted(Main.:+, a, b)::Base.Broadcast.Broadcasted{Base.Broadcast.Style{Tuple},Nothing,typeof(+),Tuple{Tuple{Int64,MyInt},Tuple{Int64,MyInt}}}
│ %2 = Base.materialize(%1)::Tuple{Union{Int64, MyInt},Union{Int64, MyInt}}
└── return %2

```

As you said, `map` makes it type stable. I was surprised `broadcast` also makes it type stable, because I thought `a .+ b` was equivalent to `broadcast(+, a, b)`. Do you happen to know why this is the case?
