Difference between `where` keyword inside or outside the curly braces

The first is a unionall type, the second is a concrete type:

julia> isconcretetype(Adjoint{Float64, Matrix{T} where T})
true

julia> isconcretetype(Adjoint{Float64, Matrix{T}} where T)
false

julia> (Adjoint{Float64, Matrix{T}} where T) isa Core.UnionAll
true

The first one is the infinite set of adjoints wrapping a Matrix of some type. In other words, we could imagine representing the infinite set in pseudocode with something like:

Set([Adjoint{Float64, T} for T in all_possible_types])

Whereas the second is an adjoint wrapping a Matrix of some type, or in other words:

Adjoint{Float64, Matrix_of_any_type}.

You almost always want the former, not the latter. There is little reason to construct an adjoint which can contain any Matrix, usually you just want an adjoint with one type.

The difference is clearer with Vector:

  • Vector{Vector{T}} where T is a unionall representing all vector of same-type vectors.
  • Vector{Vector{T} where T} is a vector containing the specific element type Vector{T} where T.

Here is an example that illustrates the difference, for the simpler case of Vector{Vector{T} where T} vs Vector{Vector{T}} where T.

First make some type aliases:

julia> const InsideWhere = Vector{Vector{T} where T}
Array{Array{T,1} where T,1}

julia> const OutsideWhere = Vector{Vector{T}} where T
Array{Array{T,1},1} where T

The default array constructor promotes elements to a common type if possible:

julia> x = [[1, 2], [1.2, 3.4]]
2-element Array{Array{Float64,1},1}:
 [1.0, 2.0]
 [1.2, 3.4]

julia> x isa InsideWhere
false

julia> x isa OutsideWhere
true

But by using a typed array literal we can avoid the automatic promotion:

julia> y = ( Vector{T} where T )[[1, 2], [1.2, 3.4]]
2-element Array{Array{T,1} where T,1}:
 [1, 2]
 [1.2, 3.4]

julia> y isa InsideWhere
true

julia> y isa OutsideWhere
false