Description
Consider the function:
julia> fibby = let
fib(n) = n≤1 ? n : fib(n-1)+fib(n-2)
end
(::var"#fib#3") (generic function with 1 method)
This is approximately equivalent to:
struct var"#fib#3" <: Function
fib :: Core.Box
end
(fib::var"#fib#3")(n) = n≤1 ? n : fib.fib.contents(n-1)+fib.fib.contents(n-2)
fibby = let
fib = var"#fib#3"(Core.Box())
fib.fib.contents = fib
end
The fact that the recursive function boxes its reference to itself here is obviously inefficient and undesirable.
My understanding is that fib
's reference to itself must be boxed because of the rule that whenever a capture is or can be assigned after its closure's instantiation, then that capture must be boxed: obviously this is usually a good rule, since the capture's new value could otherwise be unknown to the closure during the closure's lifetime. Here, lowering detects that the local fib
identifier's first and only assignment occurs after the local functor object's instantiation, so following this rule, it boxes fib
.
However, I propose we carve an exception to this rule for the function's own identifier: if the function's local identifier is only ever assigned to the functor object itself and has no other syntactical assignments anywhere, then it is obvious that the value this identifier is assigned to is always known to the closure throughout its lifetime—it is itself! Thus, with such an exception made, I would hope that lowering could be changed such that the code above would instead become approximately equivalent to:
struct var"#fib#3" <: Function end
(fib::var"#fib#3")(n) = n≤1 ? n : fib(n-1)+fib(n-2)
fibby = let
fib = var"#fib#3"()
end
(this proposal solves #47760 but more elegantly so I'm closing that issue)