Pass a 1-D view to a function defined for Array{Float,1}?

Hello,

I would like to pass a view of an Array{Float64,1} to a function defined for Array{Float64,1}, however I got a type-error by proceeding this way. Is there a generic type that includes Array{Float64,1} and its SubArray{Float64,1,Array{Float64,1}? I guess the answer will be yes but do I get the same performance for functions like f if I drop the type requirement on A?

Example:

A = rand(10)

function f(A::Array{Float64,1}) 
out =0.0
for i=1:size(A,1)
out +=exp(-A[i]^2)
end
return out
end

f(A)
f(view(A,1:5))

MethodError: no method matching f(::SubArray{Float64,1,Array{Float64,1},Tuple{UnitRange{Int64}},true})
Closest candidates are:
f(!Matched::Array{Float64,1}) at In[27]:4

Stacktrace:
[1] top-level scope at In[27]:12

There is a really simple way to do this that will not have a performance impact:

function f(A) 
    out = zero(eltype(A))
    for i=1:size(A,1)
        out +=exp(-A[i]^2)
    end
    return out
end

Note, you could also write this as

function f(A) 
    return sum(exp.(-A.^2))
end

which imo is simpler and more readable.

2 Likes

Restricting the input type has no effect on performance. It is only used for method dispatch or, frequently in my case, making sure you aren’t being an idiot by submitting a 3D array to your function that expected a 1D array.

2 Likes

Thank you for your answers. I have a related question:
Why can I pass a view of an Array{Float64,1} to a constructor without issue and can’t evaluate the previous function for views?

struct MyStruct
    A::Array{Float64,1}   
end

MyStruct(view(randn(10),1:5))

MyStruct([-0.8972133521449638, -1.147599141327517, 0.42325517918092387, -0.20243752151598818, 1.462572740440295])

There are a couple things happening here. First is that 1:5 is not an Array despite being an AbstractArray. An AbstractArray is anything that can be indexed into, and has a size (and is <:AbstractArray). An Array is a specific implementation. Ranges for example only store 3 numbers, the start, step, and stop of the range and computes indexing lazily. Similarly a view of an AbstractArray is still an AbstractArray, but also is not an Array.

The reason MyStruct(view(randn(10),1:5)) works is because the struct knows what type the field A has to be, and so will convert the input to match that type. This is unambiguous since there is only 1 type of MyStruct. Function dispatch, however never converts it’s arguments, and only uses types to determine which methods are applicable and most specific. As such, if you wanted to type this function, the best type for it would probably be something like
function f(A::AbstractVector{<:Number})

3 Likes

Each of the these suggestions have a problem:

function f(A) 
    out =0.0
    for i=1:size(A,1)
        out +=exp(-A[i]^2)
    end
    return out
end

This is not type stable, though there is little impact on performance. However, it will return a Float64 when the input is an array of Float32, for example.

This

function f(A) 
    return sum(exp.(-A.^2))
end

allocates a temporary array, and is a bit slower for it.

This one seems to be both fast and gives you the right output type:

f(A) = sum(exp(-x^2) for x in A)

To @mleprovost, just drop the type signature completely, and reintroduce it only if you need it for dispatch.

2 Likes

Good point, I fixed the type instability of the first example. The second does allocate an array, but given how clean it is, I still like it. It will also play nicer with GPU than the other versions. Do you know if there’s any chance that the transducer optimizations will make it so the allocation can be automatically elided in the future?

That would be really cool, and would further improve the awesomeness of broadcast, but I have no idea if it will happen.