First thing is that the compiler chooses method implementations based on types, and not values. So the test(false)
and test(true)
would call the same method of the test
function. You could get around this by using Val
types, a type that encodes a value, though it would still not work as you described as far as I know.
Here is an example of how it could look with Val
, I know this is not what you where after but wanted to show how you can propagate values using the types. Though I know this can cause some problems as well and shouldn’t be overused.
julia> _test(::Val{true}, array::Vector) = sum(abs2, array)
_test (generic function with 2 methods)
julia> _test(::Val{false}, array::Vector) = 10.0
_test (generic function with 2 methods)
julia> test(z::Bool=true, array::Vector=rand(1000)) = _test(Val(z), array)
test (generic function with 3 methods)
julia> a = rand(1000);
julia> @btime test(true, a) setup=(a=rand(1000))
364.286 ns (0 allocations: 0 bytes)
330.6842655375433
julia> @btime test(false, a) setup=(a=rand(1000))
236.866 ns (0 allocations: 0 bytes)
10.0
julia> @code_lowered test(true, rand(1000))
CodeInfo(
1 ─ %1 = Main.Val(z)
│ %2 = Main._test(%1, array)
└── return %2
)
julia> @code_lowered _test(Val(true), rand(1000))
CodeInfo(
1 ─ %1 = Main.sum(Main.abs2, array)
└── return %1
)
julia> @code_lowered _test(Val(false), rand(1000))
CodeInfo(
1 ─ return 10.0
)
As for the reason why the compiler can’t deduce what you want it to do and kind of fix it within the boundaries of the language (i.e. still no dispatch on value, just remove the calculation if it is not needed) I can’t really answer. It would probably be possible to make the compiler figure out that no mutations are made in the shared block of code, and that none of the variables from the shared block are used if z==false
, and then deduce that the shared block should only run if z==true
and move it into that part.
But to me this feels like the compiler start doing things without my consent that I’m not in control of. What if I add a second if-else inside the false branch that depends on another bool parameter.
function test(z::Bool=true,y::Bool=false,array::Vector=rand(1000))
local x = sum([y*y for y in array])
if z
return x
else
if y
return 10.0
else
return x
end
end
end
Should the calculation of x
be left at the start now, or should it be lifted inside both z==true
as well as z==false && y==false
, now we suddenly have more code instead and might be more susceptible to cache misses for the code, which probably don’t matter in this case but for bigger applications it might.
It quickly becomes complex and maybe unclear which is the “best” choice, so my guess is that this is the reason to leave these choices to the programmer. If you want to duplicate the code since you think that is faster or better for your case, do that, if running the code is somehow cheaper than keeping duplicates then you can choose that.
Not sure if I made it clearer, and again not my field so if anything sounds wrong it might very well be