# Type-inference with two variables that must have the same type

**URL:** <https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421>\
**Category:** General Usage\
**Tags:** question, inference, type-stability\
**Created:** [January 23, 2023, 6:45pm UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421 "2023-01-23T18:45:03Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![jishnub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jishnub/32/33620_2.png) [@jishnub](https://discourse.julialang.org/u/jishnub)\
**Post date:** [January 23, 2023, 6:45pm UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421/1 "2023-01-23T18:45:03Z")

</div>

```julia
julia> fs() = rand(Bool) ? (1,1) : (1.0, 1.0)
fs (generic function with 1 method)

julia> f(fs, i) = fs()[i]
f (generic function with 1 method)

julia> g(fs) = (f(fs, 1), f(fs, 2))
g (generic function with 1 method)

julia> @code_warntype g(fs)
MethodInstance for g(::typeof(fs))
  from g(fs) @ Main REPL[3]:1
Arguments
  #self#::Core.Const(g)
  fs::Core.Const(fs)
Body::Tuple{Union{Float64, Int64}, Union{Float64, Int64}}
1 ─ %1 = Main.f(fs, 1)::Union{Float64, Int64}
│ %2 = Main.f(fs, 2)::Union{Float64, Int64}
│ %3 = Core.tuple(%1, %2)::Tuple{Union{Float64, Int64}, Union{Float64, Int64}}
└── return %3

```

Would it be possible to nudge the compiler to realize that the types of `f(fs,1)` must be the same as that of `f(fs, 2)`, which would make the result a union of simple `Tuple`s instead of a `Tuple` of unions? I can’t add type assertions to `f` and `g`.

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [January 23, 2023, 7:06pm UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421/2 "2023-01-23T19:06:12Z")

</div>

> [@jishnub](#):
>
> the types of `f(fs,1)` must be the same as that of `f(fs, 2)`,

But that’s not the case:

```julia
julia> g(fs) 
(1, 1)

julia> g(fs) 
(1.0, 1)

```

---

<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:** [January 23, 2023, 7:18pm UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421/3 "2023-01-23T19:18:07Z")

</div>

Is that what you mean? The types are the same. Also the type inferred is correct, it cannot be narrower, as shown above as `fs` is called twice.

```julia
**julia>** T1=Tuple{Union{Float64, Int64}, Union{Float64, Int64}}

Tuple{Union{Float64, Int64}, Union{Float64, Int64}}

**julia>** T2=Union{Tuple{Int,Float64},Tuple{Int,Int},Tuple{Float64,Int},Tuple{Float64,Float64}}

Union{Tuple{Float64, Float64}, Tuple{Float64, Int64}, Tuple{Int64, Float64}, Tuple{Int64, Int64}}

**julia>** T1==T2

true

```

Now if you want `fs` to be called once:

```julia
julia> g2(fs)=(fs()...,)
julia> @code_warntype g2(fs)
MethodInstance for g2(::typeof(fs))
  from g2(fs) in Main at REPL[15]:1
Arguments
  #self#::Core.Const(g2)
  fs::Core.Const(fs)
Body::Union{Tuple{Float64, Float64}, Tuple{Int64, Int64}}
1 ─ %1 = (fs)()::Union{Tuple{Float64, Float64}, Tuple{Int64, Int64}}
│ %2 = Core._apply_iterate(Base.iterate, Core.tuple, %1)::Union{Tuple{Float64, Float64}, Tuple{Int64, Int64}}
└── return %2

```

---

<div class="post-metadata">

**Author:** ![jishnub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jishnub/32/33620_2.png) [@jishnub](https://discourse.julialang.org/u/jishnub)\
**Post date:** [January 23, 2023, 7:36pm UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421/4 "2023-01-23T19:36:32Z")

</div>

My apologies, the example was not well-chosen. This is more like what I want:

```julia
julia> using LinearAlgebra

julia> fs(A::AbstractMatrix) = isdiag(A) ? (1,1) : (1.0, 1.0)
fs (generic function with 1 method)

julia> f(A, fs, i) = fs(A)[i]
f (generic function with 2 methods)

julia> g(A, fs) = (f(A, fs, 1), f(A, fs, 2))
g (generic function with 2 methods)

julia> @code_warntype g(ones(1,1), fs)
MethodInstance for g(::Matrix{Float64}, ::typeof(fs))
  from g(A, fs) @ Main REPL[15]:1
Arguments
  #self#::Core.Const(g)
  A::Matrix{Float64}
  fs::Core.Const(fs)
Body::Tuple{Union{Float64, Int64}, Union{Float64, Int64}}
1 ─ %1 = Main.f(A, fs, 1)::Union{Float64, Int64}
│ %2 = Main.f(A, fs, 2)::Union{Float64, Int64}
│ %3 = Core.tuple(%1, %2)::Tuple{Union{Float64, Int64}, Union{Float64, Int64}}
└── return %3

```

In this example, the return type of `fs(A)` and `g(A, fs)` is known from the values of the arguments, unlike the random example above. Perhaps constant propagation is required to indicate that the same branch is chosen in both the indexing operations?

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [January 23, 2023, 9:37pm UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421/5 "2023-01-23T21:37:02Z")

</div>

But how will the compiler know that `isdiag` isn’t somehow a bit random? Or that `A` hasn’t been mutated behind the scenes, somehow.

Wouldn’t the function have to be known to be ‘pure’, and the input immutable?

---

<div class="post-metadata">

**Author:** ![jishnub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jishnub/32/33620_2.png) [@jishnub](https://discourse.julialang.org/u/jishnub)\
**Post date:** [January 24, 2023, 6:14am UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421/6 "2023-01-24T06:14:30Z")

</div>

The mutability of the argument is a red herring, as we may check:

```julia
julia> fs(x::Int) = x > 2 ? (1,1) : (1.0, 1.0)
fs (generic function with 1 method)

julia> f(x, fs, i) = fs(x)[i]
f (generic function with 1 method)

julia> g(x, fs) = (f(x, fs, 1), f(x, fs, 2))
g (generic function with 1 method)

julia> @code_warntype g(2, fs)
MethodInstance for g(::Int64, ::typeof(fs))
  from g(x, fs) @ Main REPL[3]:1
Arguments
  #self#::Core.Const(g)
  x::Int64
  fs::Core.Const(fs)
Body::Tuple{Union{Float64, Int64}, Union{Float64, Int64}}
1 ─ %1 = Main.f(x, fs, 1)::Union{Float64, Int64}
│ %2 = Main.f(x, fs, 2)::Union{Float64, Int64}
│ %3 = Core.tuple(%1, %2)::Tuple{Union{Float64, Int64}, Union{Float64, Int64}}
└── return %3

```

If you ask how the compiler may know that `>(::Int, ::Int)` isn’t a bit random, are there instances where it may be? I would have imagined that in cases like these, it may assume purity. But I don’t know much about compilers, which is why I’m wondering if there’s a way to achieve this.

---

<div class="post-metadata">

**Author:** ![fredrikekre](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fredrikekre/32/1688_2.png) [@fredrikekre](https://discourse.julialang.org/u/fredrikekre)\
**Post date:** [January 24, 2023, 8:42am UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421/7 "2023-01-24T08:42:02Z")

</div>

With constant propagation of the `2` it already works:

```julia
julia> fs(x::Int) = x > 2 ? (1,1) : (1.0, 1.0);

julia> f(x, fs, i) = fs(x)[i];

julia> g(x, fs) = (f(x, fs, 1), f(x, fs, 2));

julia> h() = g(2, fs);

julia> @code_warntype h()
MethodInstance for h()
  from h() @ Main REPL[4]:1
Arguments
  #self#::Core.Const(h)
Body::Tuple{Float64, Float64}
1 ─ %1 = Main.g(2, Main.fs)::Core.Const((1.0, 1.0))
└── return %1

```

(I thought it might have been necesarry to [force specialization on the function with `::F) where F`](https://docs.julialang.org/en/v1/manual/performance-tips/#Be-aware-of-when-Julia-avoids-specializing) but doesn’t seem like it is needed.)

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [January 24, 2023, 8:57am UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421/8 "2023-01-24T08:57:37Z")

</div>

But for a mutable input value, this fails again:

```julia
julia> fs(x::Vector) = x[1] > 2 ? (1,1) : (1.0, 1.0);

julia> h() = g([2], fs);

julia> @code_warntype h()
MethodInstance for h()
  from h() @ Main REPL[12]:1
Arguments
  #self#::Core.Const(h)
Body::Tuple{Union{Float64, Int64}, Union{Float64, Int64}}
1 ─ %1 = Base.vect(2)::Vector{Int64}
│ %2 = Main.g(%1, Main.fs)::Tuple{Union{Float64, Int64}, Union{Float64, Int64}}
└── return %2

```

So may not be a red herring after all?

Still, without a constant value for `x` it doesn’t seem to work. Specializing the function `fs` with `F<:Function` doesn’t help either.

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [January 24, 2023, 9:34am UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421/9 "2023-01-24T09:34:35Z")

</div>

Add a function inference barrier perhaps?

```julia
julia> g(A, fs) = let infer(a::T,b::T) where T = (a,b)
           infer(f(A, fs, 1), f(A, fs, 2))
       end
g (generic function with 1 method)

julia> @code_warntype g(ones(1,1), fs)
MethodInstance for g(::Matrix{Float64}, ::typeof(fs))
  from g(A, fs) @ Main REPL[133]:1
Arguments
  #self#::Core.Const(g)
  A::Matrix{Float64}
  fs::Core.Const(fs)
Locals
  infer::var"#infer#164"
Body::Union{Tuple{Float64, Float64}, Tuple{Int64, Int64}}
1 ─ (infer = %new(Main.:(var"#infer#164")))
│ %2 = Main.f(A, fs, 1)::Union{Float64, Int64}
│ %3 = Main.f(A, fs, 2)::Union{Float64, Int64}
│ %4 = (infer)(%2, %3)::Union{Tuple{Float64, Float64}, Tuple{Int64, Int64}}
└── return %4

```

---

<div class="post-metadata">

**Author:** ![jishnub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jishnub/32/33620_2.png) [@jishnub](https://discourse.julialang.org/u/jishnub)\
**Post date:** [January 24, 2023, 9:40am UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421/10 "2023-01-24T09:40:14Z")

</div>

This is similar to adding type-assertions to `g`, which I can’t do. I expect the types to be the same for this `fs` that I’m providing, but they may not be identical in general.

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [January 24, 2023, 9:53am UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421/11 "2023-01-24T09:53:16Z")

</div>

Does that mean this is a problem for multiple dispatch?

```julia
julia> g(A, any_fs) = (f(A, any_fs, 1), f(A, any_fs, 2)) # generic
g (generic function with 1 method)

julia> g(A, ::typeof(fs)) = let infer(a::T,b::T) where T = (a,b)
           infer(f(A, fs, 1), f(A, fs, 2))  
       end # specialization to your fs
g (generic function with 2 methods)

```

trial:

```julia
julia> fs(A::AbstractMatrix) = isdiag(A) ? (1,1) : (1.0, 1.0)
fs (generic function with 1 method)

julia> some_fs(A::AbstractMatrix) = isdiag(A) ? (1,1) : (1.0, 1.0)
some_fs (generic function with 1 method)

julia> @code_warntype g(ones(1,1), fs)
MethodInstance for g(::Matrix{Float64}, ::typeof(fs))
  from g(A, ::typeof(fs)) @ Main REPL[141]:1
Arguments
  #self#::Core.Const(g)
  A::Matrix{Float64}
  _::Core.Const(fs)
Locals
  infer::var"#infer#168"
Body::Union{Tuple{Float64, Float64}, Tuple{Int64, Int64}}
1 ─ (infer = %new(Main.:(var"#infer#168")))
│ %2 = Main.f(A, Main.fs, 1)::Union{Float64, Int64}
│ %3 = Main.f(A, Main.fs, 2)::Union{Float64, Int64}
│ %4 = (infer)(%2, %3)::Union{Tuple{Float64, Float64}, Tuple{Int64, Int64}}
└── return %4

julia> @code_warntype g(ones(1,1), some_fs)
MethodInstance for g(::Matrix{Float64}, ::typeof(some_fs))
  from g(A, any_fs) @ Main REPL[140]:1
Arguments
  #self#::Core.Const(g)
  A::Matrix{Float64}
  any_fs::Core.Const(some_fs)
Body::Tuple{Union{Float64, Int64}, Union{Float64, Int64}}
1 ─ %1 = Main.f(A, any_fs, 1)::Union{Float64, Int64}
│ %2 = Main.f(A, any_fs, 2)::Union{Float64, Int64}
│ %3 = Core.tuple(%1, %2)::Tuple{Union{Float64, Int64}, Union{Float64, Int64}}
└── return %3

```

---

<div class="post-metadata">

**Author:** ![jishnub](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jishnub/32/33620_2.png) [@jishnub](https://discourse.julialang.org/u/jishnub)\
**Post date:** [January 24, 2023, 10:12am UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421/12 "2023-01-24T10:12:16Z")

</div>

Yes, specializing this way definitely works, but it’s really a last resort 🙂 I was hoping that this may be handled at a lower level

---

<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:** [January 24, 2023, 3:37pm UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421/13 "2023-01-24T15:37:36Z")

</div>

Seems to really be mostly offended by `f(...,fs,1)` and `f(...,fs,2)` since 1 and 2 are in the value domain after all and not in the type domain.

This seems to work:

```julia
julia> g(x,fs)=(fs(x)...,)
julia> @code_warntype g(2, fs)
MethodInstance for g(::Int64, ::typeof(fs))
  from g(x, fs) in Main at REPL[15]:1
Arguments
  #self#::Core.Const(g)
  x::Int64
  fs::Core.Const(fs)
Body::Union{Tuple{Float64, Float64}, Tuple{Int64, Int64}}
1 ─ %1 = (fs)(x)::Union{Tuple{Float64, Float64}, Tuple{Int64, Int64}}
│ %2 = Core._apply_iterate(Base.iterate, Core.tuple, %1)::Union{Tuple{Float64, Float64}, Tuple{Int64, Int64}}
└── return %2

```

---

<div class="post-metadata">

**Author:** ![jameson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jameson/32/23_2.png) [@jameson](https://discourse.julialang.org/u/jameson)\
**Post date:** [January 25, 2023, 4:32am UTC](https://discourse.julialang.org/t/type-inference-with-two-variables-that-must-have-the-same-type/93421/14 "2023-01-25T04:32:31Z")

</div>

It would be quite difficult for it to realize those are the same expression, with no mutation in between (aka permitting invariant hoisting and then common subexpression elimination or CSE). You can try to assert that one isa typeof(other), but we have no support at all for that in inference (dependency or reverse dataflow constraints even just between 2 variables are somewhat shockingly hard to integrate into the analysis)
