Manual type inference in Julia

359 Views Asked by At

Julia allows for easy construction of arrays using comprehension syntax:

A = [ sqrt(i)^2+j for i in 0:10, j in 1:3 ]

I assume that for type stable expressions the compiler first determines the type of the generic element, the length of the iterable, and then pre-allocates and initialises the array and its content.

Sometimes, however, a nested for loop is preferred. This could be because the array has many dimensions, or because the second iterable's length depends on the first, or becasue the expression is complicated and computing it has side effects.

In those case the above code can be expanded into

T = ???
A = Array{T}(11,3)
for i in 0:10
  for j in 1:3
    A[i,j] = sqrt(i)^2 + j
  end
end

How can T be computed in a simple, readable, easily maintainable fashion? Is there a way to manually invoke the type inference mechanism used under the hood of list comprehensions? Solutions I consider unsatisfactory are:

T = Float64 # Not my job to do the compiler's work...
T = typeof(sqrt(0)^2+1) # Or is the argument of typeof not evaluated in this case?

Something along the line of the c++ decltype mechanism perhaps?

2

There are 2 best solutions below

1
On

Actually, it's not that simple. Comprehensions used to pick the element type using type inference, but as I understand it, has moved away from that.

Now (I believe), the initial element type is taken as the type of the first element. When needed, the element type is widened and the array reallocated. If the resulting element type is a concrete type that can be inferred by type inference, the resulting code is still as efficient as if the element type were based on type inference directly.

The reason that comprehensions have moved away from direct reliance on type inference is that it is meant to be an optimization to make Julia code run faster, not part of Julia's semantics. In practice, julia is not always able to infer a tight type, and inference results may vary with julia versions, so it's not desirable to make code behavior depend on it (though performance will, of course).

Considering your original problem: I often find that it makes for cleaner code with less surprises if you let the user supply the result type T in a case like this. The machinery that I have described for comprehensions is also used by map (and more or less by broadcast, but they're not quite consistent so far), but it has not been exposed directly to users (no idea if it would make sense to do so).

0
On

There is the unexported undocumented Base.return_types. It is used a lot in tests, so search in the tests directory to find usages. Please note Toivio's answer and be prepared that it inference might give up and just return Any for the same expression in a new version of Julia.