miya
miya

Reputation: 81

How to declare array type that can have int and floats

I am a newbie to Julia and still trying to figure out everything. I want to restrict input variable type to array that can contain int and floats. I would really appreciate any help.

function foo(array::??)

Upvotes: 3

Views: 1700

Answers (2)

Chris Rackauckas
Chris Rackauckas

Reputation: 19162

As I mentioned in the comment, you don't want to mix them for performance reasons. However, if your array can be either Floats or Ints, but you don't know which it will be, then the best approach is to make it dispatch on the parametric type:

function foo{T<:Number,N}(array::Array{T,N})

This will make it compile a separate function for arrays of each number type (only when needed), and since the type will be known for the compiler, it will run an optimized version of the function whether you give it foo([0.1,0.3,0.4]), foo([1 2 3]), foo([1//2 3//4]), etc.


Updated syntax in Julia 0.6+

function foo(array::Array{T,N}) where {T<:Number,N}

For more generality, you can use Array{Union{Int64,Float64},N} as a type. This will allow Floats and Ints, and you can use its constructor like

arr = Array{Union{Int64,Float64},2}(4,4) # The 2 is the dimension, (4,4) is the size

and you can allow dispatching onto weird things like this as well by doing

function foo{T,N}(array::Array{T,N})

i.e. just remove the restriction on T. However, since the compiler cannot know in advance whether any element of the array is an Int or a Float, it cannot optimize it very well. So in general you should not do this...


But let me explain one way you can work with this and still get something with decent performance. It also works by multiple dispatch. Essentially, if you encase your inner loops with a function call which is a strictly typed dispatch, then when doing all of the hard calculations it can know exactly what type it is and optimize the code anyways. This is best explained by an example. Let's say we want to do:

function foo{T,N}(array::Array{T,N})
  for i in eachindex(array)
    val = array[i]
    # do algorithm X on val
  end
end

You can check using @code_warntype that val will not compile as an Int64 or Float64 because it won't know until runtime what type it will be for each i. If you check @code_llvm (or @code_native for the assembly) you see that there is a really long code that is generated in order to handle this. What we can instead do is define

function inner_foo{T<:Number}(val::T)
  # Do algorithm X on val
end

and then instead define foo as

function foo2{T,N}(array::Array{T,N})
  for i in eachindex(array)
    inner_foo(array[i])
  end
end

While this looks the same to you, it is very different to the compiler. Note that inner_foo(array[i]) dispatches a specially-compiled function for whatever number type it sees, so in foo2 algorithm X is calculated efficiently, and the only non-efficient part is the wrapping above inner_foo (so if all your time is spent in inner_foo, you will get basically maximal performance).

This is why Julia is built around multiple-dispatch: it's a design which allows you to push things out to optimized functions whenever possible. Julia is fast because of it. Use it.

Upvotes: 14

Greg
Greg

Reputation: 91

This should be a comment to Chris' answer, but I don't have enough points to comment.

As Chris points out, using function barriers can be quite useful to generate optimal code. However be aware that dynamic dispatch has some overhead. This may or may not be important depending on the complexity of the inner function.

function foo1{T,N}(array::Array{T,N})
    s = 0.0
    for i in eachindex(array)
        val = array[i]
        s += val*val
    end
    s
end

function foo2{T,N}(array::Array{T,N})
    s = 0.0
    for i in eachindex(array)
        s += inner_foo(array[i])
    end
    s
end

function foo3{T,N}(array::Array{T,N})
    s = 0.0
    for i in eachindex(array)
        val = array[i]
        if isa(val, Float64)
            s += inner_foo(val::Float64)
        else
            s += inner_foo(val::Int64)
        end
    end
    s
end

function inner_foo{T<:Number}(val::T)
    val*val
end

For A = Array{Union{Int64,Float64},N}, foo2 doesn't provide much speedup over foo1 since benefit of the optimised inner_foo is countered by the cost of dynamic dispatch.

foo3 is much faster (~7 times) and could be used if possible types are limited and known ahead of time (as in above example where elements are either Int64 or Float64)

See https://groups.google.com/forum/#!topic/julia-users/OBs0fmNmjCU for further discussion.

Upvotes: 6

Related Questions