Julia: Arrays with abstract parameters cause errors but variables with abstract types don't

julia functor
julia mutable array
julia declare struct
julia product of array
julia struct in struct
methoderror: no method matching
julia objects
julia array of arrays

The Julia style guide says the following:

Don’t use unnecessary static parameters. A function signature:

foo{T<:Real}(x::T) = ...

should be written as:

foo(x::Real) = ...

I expected that to apply to array parameters too. However, if I write the following code:

ret1(x::Real) = x
ret2(x::Array{Float64,2}) = x
ret3(x::Array{Real,2}) = x
ret1(1.0)
ret2(rand(2,2))
ret3(rand(2,2))

then I get the following console output (using Julia 0.2.1):

MethodError(ret3,(
2x2 Array{Float64,2}:
 0.841121  0.322133
 0.469432  0.495438,))

So why does Julia throw an error for arrays with abstract type parameters, but not for variables with abstract types?


In the case of ret3, the type parameter is actually necessary because an Array{Real} is a type that can never be constructed Julia's type parameters are invariant. This is a bit of a subtle topic but the key fact is that while Float64 <: Real is true, Array{Float64} <: Array{Real} is not. This is a bit confusing at first, but this is necessary for the compiler to be able to know the memory layout of function arguments when doing code generation. See the manual for more on this.

So you can dispatch on a Real as in ret1 because when you pass it a Float64, Float64 <: Real is true, whereas in ret3 you are passing it a Array{Float64}, and Array{Float64} <: Array{Real} is not the case, hence the no method error. To fix this, use a type parameter:

julia> ret3{T <: Real}(x::Array{T,2}) = x
ret3 (generic function with 2 methods)

julia> ret3(rand(2,2))
2x2 Array{Float64,2}:
 0.0857132  0.353194
 0.802737   0.717292

Types · The Julia Language, Julia: Arrays with abstract parameters cause errors but variables with abstract types don't - julia. The output tells us that the arrays are of types Array{Int64,1} and Array{Float64,1} respectively. Here Int64 and Float64 are types for the elements inferred by the compiler. We’ll talk more about types later. The 1 in Array{Int64,1} and Array{Any,1} indicates that the array is one dimensional (i.e., a Vector).


In the case of ret3, a strictly Array{Real, 2} is expected, i.e., an array which can hold any kind of Real variables inside (while rand(2,2) is an array of Float64 only).

In this case the static parameter is not unnecessary:

ret3{T<:Real}(x::Array{T,2}) = x

Performance Tips · The Julia Language, Julia's type system is dynamic, but gains some of the advantages of static type to a variable with a declared type: the value is always converted to Float64 . Let's consider some of the abstract types that make up Julia's numerical hierarchy​: An immutable object might contain mutable objects, such as arrays, as fields. Avoid containers with abstract type parameters. When working with parameterized types, including arrays, it is best to avoid parameterizing with abstract types where possible. Consider the following: julia> a = Real[] 0-element Array{Real,1} julia> push!(a, 1); push!(a, 2.0); push!(a, π) 3-element Array{Real,1}: 1 2.0 π


As of julia 1.2, I believe the appropriate typing for ret3 is:

ret3(x::Array{<:Real,2})

Performance Tips, Variables should be local, or passed as arguments to functions, whenever possible. If you don't see another reason for the allocations, suspect a type problem. Because a is a an array of abstract type Real , it must be able to hold any Real value. This can often be useful, but it does have a downside: for objects of type  Conventionally, Julia's arrays are indexed starting at 1, whereas some other languages start numbering at 0, and yet others (e.g., Fortran) allow you to specify arbitrary starting indices. While there is much merit in picking a standard (i.e., 1 for Julia), there are some algorithms which simplify considerably if you can index outside the range


Performance tips, Variables should be local, or passed as arguments to functions, whenever possible. If you don't see another reason for the allocations, suspect a type problem. Because a is a an array of abstract type Real , it must be able to hold any Real value. This can often be useful, but it does have a downside: for objects of type  Abstract classes define the behavior, and types themselves mix the two together. The point of mixing in traits to your class is their fields, so you would have to know about their fields anyways. In Scala traits don't allow constructors in order to simplify the construction rules, and it might make sense to do something similar.


Interfaces for Abstract Types · Issue #6975 · JuliaLang/julia · GitHub, Variables should be local, or passed as arguments to functions, whenever possible. If you don't see another reason for the allocations, suspect a type problem. The Lint package can also warn you of certain types of programming errors. Because a is a an array of abstract type Real , it must be able to hold any Real  Concrete and abstract types . Each object in Julia (informally, this means everything you can put into a variable in Julia) has a type. But not all types can have a respective object (instances of that type). The only ones that can have instances are called concrete types. These types cannot have any subtypes. The types that can have subtypes (e.g.


Types · The Julia Language, On the abstract type definition one has to keep track of the interface names/​signatures. 2-element Array{(DataType,DataType),1}: (A,Int64) (A,Float64) julia​> amount of confusion caused by having to import generic functions – but not So if methods don't belong to types, how can interfaces (which are  Storage: Arrays and Tuples []. In Julia, groups of related items are usually stored in arrays, tuples, or dictionaries. Arrays can be used for storing vectors and matrices.