Type stable mapreduce over NamedTuple keys

I am trying to write a function that

  1. picks keys from a NamedTuple,
  2. uses each key to obtain values from two objects (not necessarily NamedTuples, they support getproperty, may have other properties not used by this function)
  3. applies a function to each triplet,
  4. sums the result.

Example that is not type stable:

function ntpicker_naive(nt, a, b)
    mapreduce(k -> getproperty(a, k) * getproperty(b, k) * getproperty(nt, k), +,
              keys(nt))
end

I came up with a recursive implementation that is type stable for short named tuples, but not long ones:


@inline _ntpicker(acc, a, b, ::NamedTuple{(),Tuple{}}) = acc

@inline function _ntpicker(acc, a, b, nt::NamedTuple{K}) where K
    K1 = first(K)
    v = values(nt)
    p1 = getproperty(a, K1) * getproperty(b, K1) * first(v)
    _ntpicker(acc + p1, a, b, NamedTuple{Base.tail(K)}(Base.tail(v)))
end

ntpicker(nt::NT, a, b) where {NT <: NamedTuple} = _ntpicker(0, a, b, nt)

nt2 = (c = 1, d = 2)
nt4 = (c = 1, d = 2, e = 3, f = 4)

@code_warntype ntpicker(nt2, nt2, nt2) # OK
@code_warntype ntpicker(nt4, nt4, nt4) # Any

Is there a way I could make this work for arbitrarily long NamedTuples, or should I just use generated functions?