How is it that new Julia programmers tend to abuse type annotations?

There actually is an optimization which does this, it’s known as “world splitting” (which is different from union splitting!). What world splitting does is it looks at a call like f(::Any) and then it looks at the methods of f. If f does not have very many methods, and they all return the same (or similar) types, then the optimizer will use that information to optimize the call.

Here’s a demo of it in action:

f(::Int) = 1;
f(::Float64) = 2;
f(::Complex) = 3;

and then

julia> Core.Compiler.return_type(f, Tuple{Any})
Int64

Voila! Unfortunately, now with the benefit of hindsight, this optimization is often seen as a kinda bad idea. The problem is that the result of this optimization depends strongly on non-local information, and can suddenly fail and de-optimize if someone somewhere else defines a method.

Basically, suppose someone has code that does something like string(::Any), and the compiler is able to infer that into string(::Any)::String. Now suppose you compiled code that relies on this result.

Problems can then occur if you load a package which adds a method to string that doesn’t return a string. Suppose it was a symbolic package and they defined some lazy-symbolic thing such that string(::Sym)::Sym or something. Now suddenly your compiled code is invalid and needs to be re-compiled before it can be used.

Situations like this may seem rare, but it’s actually been a quite big source of hard-to-fix latency bugs out there in a lot of julia packages.


However, there has recently been some talk about creating a way to statically guarentee that a function like string always returns a String or that constructors FloatXX always return an object of that type, this’d be nice, but doesn’t exist yet.

20 Likes