According to Output type in the manual I should use promote_op to decide container types. But according to the output of help?> Base.promote_op I should avoid promote_op as it is “fragile”. And if I did use promote_op is the resulting function performant or do I need to define some new inner functions (for function barriers)?
Another style is function foo( T, cont1, cont2 )..., or, should I define it as function foo( ::Val{T}, cont1, cont2 ) where{T}...?
I have read the “Performance Tips” section multiple times as well as “Design Patterns with Parametric methods” but I am still quite confused.
Thanks for any advice.
–shiv–
Using @code_warntype it seems a good way to organize the code is as follows:
function foo( T, x, y )
acc = zero( T )
for i in 1:length(x)
acc += T( x[i] * y[i] )
end
return acc
end
Here I pass the output type explicitly as the first argument. In case the user “messes up” and passes a lesser type than what promote_op would have chosen it is good to explicitly convert the summand to acc, hoping that LLVM will discard unneeded type conversions as no-ops. Declaring acc :: T only seems to insert unnecessary type assertions.
I also noticed that replacing T with ::Val{T} as the first argument seemed to have no effect on code generation but makes the interface a bit more complex to use.
I am hoping that these observations will generalize to more complex situations…
–shiv–
The Type{T} annotation forces type specialization, otherwise it’s equivalent to writing foo(T::DataType, ... which will treat all types the same, as values of type DataType.
It’s possible Val{T} can achieve something similar (never tried that), but you would need to call the function with Val(T).
And for the sake of completeness code with promote_op:
function foo( x, y )
op( a, b ) = a*b + a*b
T = Base.promote_op( op, eltype(x), eltype(y) )
acc = zero( T )
for i in 1:length(x)
acc += x[i] * y[i]
end
return acc
end
promote_op is used in many places in Base, so lots of functions rely on it already.
If specifying the resulting type manually is sometimes (but not always) useful, you can nicely do
foo(x, y) = foo(promote_op(*, eltype(x), eltype(y)), x, y) # determine T automatically
foo(::Type{T}, x, y) = ... use T type ...