The idea behind this generated function

Inspecting the source code of ForwardDiff.jl) , we can found this function

@generated function single_seed(::Type{NTuple{N,V}}, ::Val{i}) where {N,V,i}
           ex = Expr(:tuple, [ifelse(i === j, :(one(V)), :(zero(V))) for j in 1:N]...)
           return :(NTuple($(ex)))
       end

which returns tuples with variable length of zeros with just one at specified location i

What is benefits of using @generated while it can be implemented as regular function, And get the results like

function single_seed_not_gen(::Type{NTuple{N,V}}, ::Val{i}) where {N,V,i}
           return tuple([ifelse(i == j , one(V), zero(V)) for j in 1:N]...)
       end

with results

julia> single_seed_not_gen(NTuple{10,Int} ,Val(4))
(0, 0, 0, 1, 0, 0, 0, 0, 0, 0)

julia> single_seed_not_gen(NTuple{20,Int} ,Val(4))
(0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)

This is very inefficient. Not only does it first heap-allocate an array before converting it to a tuple, but it is also type-unstable (because constructing it in this way means that the compiler is not able to infer the length of the tuple).

However,

return ntuple(j -> i == j ? one(V) : zero(V), Val{N}())

should be efficient and type stable. I’m not sure why they went for a @generated function instead.

6 Likes

Note that ntuple is implemented as a generated function IIRC, but yes it is a better choice.

Likely the code there existed before ntuple or before it was reliable

1 Like

The basic answer to most questions around why did ForwardDiff (or StaticArrays) do this is that the code predates Julia’s ability to express the cleaner version.

4 Likes

Only for generated callers:

So the most general implementation is just Tuple(f(i) for i = 1:(N::Int)), not counting the error checks.

1 Like

that’s make sense, Thank you.