I have a function that typically gives a value of a different type, but repeated application eventually hits a fixpoint. Say the function is called next
. I need the fixpoint in order to statically build a loop like
@generated function main_function(a, depth::StaticInt{n}) where {n}
nsteps = max(n, 0)
quote
$(Expr(:meta, :inline))
Base.Cartesian.@nexprs $nsteps i -> begin
result += foo(a)
a, b = b, next(b)
end
return result
end
end
For a toy version of the problem, we can write
struct A{N} end
@inline next(::A{0}) = A{0}()
@inline next(::A{N}) where {N} = A{N-1}()
and depth
can be something like
@inline function depth(ÎĽ::M) where {M}
return depth(ÎĽ, next(ÎĽ), static(0))
end
@inline function depth(μ::M, β::M, s::StaticInt{N}) where {M,N}
s
end
@generated function depth(μ::M, β::B, ::StaticInt{N}) where {M,B,N}
s = Expr(:call, Expr(:curly, :StaticInt, N + 1))
quote
$(Expr(:meta, :inline))
depth(β, next(β), $s)
end
end
But this doesn’t work great:
julia> using Test
julia> for n in 1:5
@inferred depth(A{n}())
end
ERROR: return type StaticInt{3} does not match inferred return type Any
Stacktrace:
[1] error(s::String)
@ Base ./error.jl:33
[2] top-level scope
@ ./REPL[122]:2
For the real application, I have this working up to a depth of 4 ,but 5 fails in the same way as this. I need this to be fully static, or else main_function
will be slower than building the loop dynamically. Also, in this case I could leverage the structure of A
, but that’s fake anyway and doesn’t work for the real problem.
I also tried Cthulhu, which gave me messages like
[constprop] Disabled by argument and rettype heuristics
[constprop] Disabled by entry heuristic (unimprovable return type)
[constprop] Bounded recursion detected. Call was widened to force convergence.
But I don’t see any way to get more detail on these, or information about how to fix the problem.
Any ideas?