Is there some annotation that I can use to give permission to the compiler to elide e.g. x = foo(y)
if x
is not used? And would it possibly work across function-call boundaries? I’d like to do something like
function foo(input)
println("foo")
2 * input
end
function bar(input)
println("bar")
2 + input
end
function baz(x, y)
println("baz")
x / y
end
function compute(input, callback)
x = foo(input)
y = bar(input)
z = baz(x, y)
return callback((;x, y, z))
end
and depending on what fields callback
accesses, perhaps only foo
is called. Or only bar
. Or both. Or all three.
Concretely, currently:
julia> compute(10, nt -> nt.x)
foo
bar
baz
20
but I’d like to annotate the function calls so that only foo
is printed.
I could turn all computations into thunks:
function foo_thunk(input)
()->(println("foo"); 2 * input())
end
function bar_thunk(input)
()->(println("bar"); 2 + input())
end
function baz_thunk(x, y)
()->(println("baz"); x() / y())
end
function compute_thunk(input, callback)
x = foo_thunk(()->input)
y = bar_thunk(()->input)
z = baz_thunk(x, y)
return callback((;x, y, z))
end
julia> compute_thunk(10, nt -> nt.x() + nt.z())
foo
baz
foo
bar
21.666666666666668
but I do not want foo
to be computed twice. Caching/memoizing the thunks would be a solution, but I do not think I’m looking for that. I want to avoid the dynamic memory allocations and dynamic checks for whether values have already been computed. Otherwise I’m relying on other compiler optimizations like escape analysis, which would likely give suboptimal results due to the function-call boundary. (Though for any of this to make sense, callback
has to be inlined anyway)
Truly, I’m looking for a solution that is basically equivalent to deleting any unused fields from the NamedTuple
, and deleting the lines where the values for those fields are computed.
I don’t expect anything to be elided if there is e.g. any getproperty(nt, fieldname)
, for a fieldname
that is not marked Core.Const
.
I’m all but certain that Base.@pure
is the wrong answer.