How to define outer constructor based on array size?


#1

Consider the parametric type below:

immutable Foo{N}
  A::AbstractMatrix{Float64}
  b::AbstractVector{Float64}

  Foo(A,b) = size(A) == (N,length(b)) ? new(A,b) : error("incorrect size")
end

Because I defined the inner constructor, I can do:

Foo{3}(eye(3),ones(3)) # works
Foo{2}(eye(3),ones(3)) # fails as expected

but I cannot do:

Foo(eye(3),ones(3)) # fails but I would like it to work

How to solve this problem?


#2

That is because you provide no means to promote the number of rows to the type-parameter N, thus the constructor does not know what N is supposed to be.

You could create an outer constructor to do that promotion

Foo(A,b) = Foo{size(A,1)}(A,b)
julia> Foo(eye(3),ones(3))
Foo{3}([1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0],[1.0,1.0,1.0])

Note, however, that this kind of value promotion to a type parameter poisons the type-inference, since the value is not known at compile-time, but instead evaluated at runtime.
see @code_warntype Foo(eye(3),ones(3))


#3

@Evizero thank you for the clear answer. Is there any trick for exposing the actual size of the array to the type system?


#4

Well in your case using Foo{3}(eye(3),ones(3)) would be the trick. More generally you could consider using https://github.com/JuliaArrays/StaticArrays.jl if it fits your purposes.

Other than that the type-inference “issue” may not be an issue at all depending on your use case.
In my personal experience type-stability turned out to either be a huge issue or absolutely none at all.
My advice: Ask yourself how often and where this code will be executed. If the answer is “a few times in the top level scope of my script” then chances are you won’t notice its influence. However, if this code is to be executed in some inner function that is called very often, then it is likely a problem that should be redesigned.

In other words: make sure that the functions that are doing the heavy computations are themself type-stable. If that is the case, the it may not matter so much that they are invoked using dynamic dispatch (which would be the consequence when calling a function with the result of Foo(eye(3),ones(3))) . The key is benchmarking your code before trying to optimize it.


#5

That is not a concrete type. You probably want stricter typing on your types. This right here would be the cause of type-instabilities and inference issues.


#6

Thank you @ChrisRackauckas, I will keep this in mind. Ideally I would like to have generic matrices that could be stored arbitrarily on disk, sparse, etc, but if that is a big bottleneck, I will use a concrete type.


#7

The best way of having concrete types for those fields of your type, is to use parameterization.
That way, you can have all the flexibility of being able to use different types (such as structures backed up on disk, or sparse, or both [my specialty! ;-)]), but still have all of the advantages of using concrete types, as @ChrisRackauckas pointed out.