Array reductions (sum, mean, etc.) and abstract functions

indexing
array

#1

I was trying to implement what @mbauman suggested with respect to array reductions (sum, mean, etc.). Here is my idea at an implementation:

Base.getindex(A::Array, args::Union{T, Colon}...) where T<:Function = T(A, tuple(find((args .== T))...))

julia> A[sum,:] == sum(A, 1)
julia> A[:,mean] == mean(A, 2)

This has two problems:

  1. Any Function could be used, not just reductions. It would be nice to be able to ‘subtype’ functions and dispatch on abstract functions like Reduction. I have not found any reference to abstract functions - what are your thoughts about that?

  2. Calling the function using T() does not work, as the type of the function is e.g. Base.#sum and not Base.sum. How can i call a function only having access to its type?


#2

Instead of trying to call T, you need to call args[n] for the appropriate argument number. A naive implementation is:

julia> Base.getindex(A::AbstractArray, args::Union{T,Colon}...) where {T<:Function} = (f=first(find(typeof.(args).==T)); args[f](A, f) )

julia> A = eye(5);

julia> A[:,sum]
5×1 Array{Float64,2}:
 1.0
 1.0
 1.0
 1.0
 1.0

#3

I think it’s kind of nice that it doesn’t only work on reductions, consider, e.g., A[diff, :] == diff(A, 1), similar for cumsum, cumprod etc. Thus works with @jmert implementation above.


#4

Maybe SimpleTraits.jl could be used to specify which functions are suitable to use for this kind of operation. The defined getindex method would then dispatch on that trait. All functions that operate along a dimension of an Array and produce sensible output could be added to the trait.


#5

Thanks for your answer. I knew that this way would work, but i think it feels a little cumbersome. The question remains: Is there a way to directly call a Function from its type?
Why does the type contain a #?


#6

Because the type is not actually a constructable type, so the symbol also has the comment character to avoid letting it be typable?? (I don’t actually know, but that sounds like a not-unreasonable guess.)

Just because I was curious, I managed to get rid of the broadcasting, but it probably looks more cumbersome (though @code_warntype suggest it’s actually more efficient).

Base.getindex(A::AbstractArray, args::Union{T,Colon}...) where {T<:Function} = getindex(A, args)

function Base.getindex(A::AbstractArray, args::Tuple{Vararg{Union{T,Colon}}}) where T<:Function
  fn, dim = _getindex_isolatefn(A, args)
  fn(A, dim)
end

@inline function _getindex_isolatefn(A, args)
  fn, post = _ge_postfn(args...)
  n = length(args) - length(post)
  return (fn, n)
end

@inline _ge_postfn(arg::Colon, tail...) = _ge_postfn(tail...)
@inline _ge_postfn(arg::Function, tail...) = (arg, tail)