3 ms·
Julia does this. The only caveat is that if you write function foo(a, xs) function bar(b) b * a end res = 0 for
by eigenspace 6y ago
Julia does this. The only caveat is that if you write
function foo(a, xs)
function bar(b)
b * a
end
res = 0
for x in xs
res += bar(x)
end
res
end
what the author desires (the lifting of a to a compile time constant) won't happen automatically because compiling a new bar for every value of a is going to be a bad idea unless the loop over xs takes longer than compiling a function. Hence, we make people 'opt-in' to this by lifting a from a runtime variable to a type parameter (julia types are parametric and their parameters can contain values).
Hence, one way to do this would be to write
foo(a, xs) = foo(Val{a}, xs)
function foo(::Type{Val{a}}, xs) where {a}
function bar(b)
b * a
end
res = 0
for x in xs
res += bar(x)
end
res
end
Now, when you run foo(2, v) (where v is some iterable), this will turn into foo(Val{2}, v) which then gets specialized on the type parameter {2}, and then can compile a new bar function that's aware of the value of a.
Here's an example at the repl where we can inspect the generated code more easily:
julia> f(a) = f(Val{a})
f (generic function with 1 method)
julia> function f(::Type{Val{a}}) where {a}
function g(b)
b * a
end
end
f (generic function with 2 methods)
julia> g1 = f(1)
(::var"#g#4"{1}) (generic function with 1 method)
julia> @code_llvm debuginfo=:none g1(4)
define i64 @julia_g_442(i64 signext %0) {
top:
ret i64 %0
}
julia> g2 = f(2)
(::var"#g#4"{2}) (generic function with 1 method)
julia> @code_llvm debuginfo=:none g2(4)
define i64 @julia_g_444(i64 signext %0) {
top:
%1 = shl i64 %0, 1
ret i64 %1
}
Notice that g1 and g2 have different optimizations applied that depend on the different runtime values of a.