Matrix Types in Function Declarations

136 views
Skip to first unread message

Trendy Moniker

unread,
Nov 26, 2013, 11:08:11 PM11/26/13
to julia...@googlegroups.com
Hi Everyone,
Noob question here.  I'm trying to figure out why this doesn't work:
a = [1 2 3; 4 5 6]
function do_stuff( m::Matrix{
Integer} )
    return m * 4
end
do_stuff(a)

but this does:
a = [1 2 3; 4 5 6]
function do_stuff2{T<:Integer}( m::Matrix{T} )
    return m * 4
end
do_stuff2(a)


I understand the semantics of the generic declaration in the second statement, but I'm hoping for 1) a clearer understanding of why the first won't work and 2) a pointer toward best practices for this pattern.

Thanks a ton!

--TM

Stefan Karpinski

unread,
Nov 26, 2013, 11:41:45 PM11/26/13
to Julia Users
This is because Julia's parametric types are invariant [1]. Matrix{Integer} means a matrix that can hold any kind of Integer. Since your matrix `a` is of type Matrix{Int} – i.e. it can only hold Ints – it is neither a subtype nor a supertype of Matrix{Integer}, it's just a different type. The quantified method applies to Matrix{T} for any T<:Integer and since Int is a subtype of Integer, it applies.

This question and answer should probably go in the FAQ, but I'm too tired to do it right now.

Trendy Moniker

unread,
Nov 27, 2013, 12:22:11 AM11/27/13
to julia...@googlegroups.com
Hi Stefan,
Thanks for the quick reply.  It's quite helpful.  If there's a brewing FAQ post, I'd suggest also adding a link to the section on parametric composite types which talks about this very issue (and which I just found for the first time in the intervening hour -- my apologies for the spam).

I'm still a bit confused on what best practices are for this code pattern.  The style guide says to avoid overly specific types, and not to use unnecessary static parameters.  At the same time, the parametric composite types entry suggests that there can be significant performance hits for declaring arrays of abstract types.  My initial impression had been that  a definition like function do_stuff2{T<:Integer}( m::Matrix{T} ) caused the compiler to create one version of the function for each concrete type under Integer, (e.g.: Int8, UInt8, Int16, etc.) but reading the parametric composite types entry and looking at methods(do_stuff2) makes me think that was wrong. 

For high-performance functions which can operate on multiple types, then, would the best solution be to create many versions of the function definition, one for each desired concrete type?  E.g.:
function do_stuff2{Int32}( m::Matrix{Int32} ); m*4; end
function do_stuff2{Int64}( m::Matrix{Int64} ); m*4; end

function do_stuff2{Float32}( m::Matrix{Float32} ); m*4; end
function do_stuff2{Float64}( m::Matrix{Float64} ); m*4; end


This seems like it could easily get out of hand.  (Side question: if this is the best solution, is it sufficient to write do_stuff2(m::Matrix{Float32}) in place of do_stuff2{Float32}(m::Matrix{Float32})?)

Thanks again for all the help.

Best,
TM

Tim Holy

unread,
Nov 27, 2013, 8:02:07 AM11/27/13
to julia...@googlegroups.com
Using _concrete_ parametric types is the way to go---the compiler will
generate all those different function versions for you. Check out the sections
in the FAQ (part of the manual) that discuss how types interact with the
compiler, those should answer your questions.

--Tim

On Tuesday, November 26, 2013 09:22:11 PM Trendy Moniker wrote:
> Hi Stefan,
> Thanks for the quick reply. It's quite helpful. If there's a brewing FAQ
> post, I'd suggest also adding a link to the section on parametric composite
> types<http://docs.julialang.org/en/latest/manual/types/#parametric-composite
> -types>which talks about this very issue (and which I just found for the
> first time in the intervening hour -- my apologies for the spam).
>
> I'm still a bit confused on what best practices are for this code pattern.
> The style guide says to avoid overly specific
> types<http://docs.julialang.org/en/latest/manual/style-guide/#avoid-writing
> -overly-specific-types>, and not to use unnecessary static
> parameters<http://docs.julialang.org/en/latest/manual/style-guide/#don-t-us
> e-unnecessary-static-parameters>. At the same time, the parametric composite
> types<http://docs.julialang.org/en/latest/manual/types/#parametric-composit
> e-types>entry suggests that there can be significant performance hits for
> declaring arrays of abstract types. My initial impression had been that a
> definition like function do_stuff2{T<:Integer}( m::Matrix{T} ) caused the
> compiler to create one version of the function for each concrete type under
> Integer, (e.g.: Int8, UInt8, Int16, etc.) but reading the parametric
> composite
> types<http://docs.julialang.org/en/latest/manual/types/#parametric-composit
> e-types>entry and looking at methods(do_stuff2) makes me think that was
> > <trendy...@gmail.com<javascript:>>

Steven G. Johnson

unread,
Nov 27, 2013, 9:20:22 AM11/27/13
to julia...@googlegroups.com


On Wednesday, November 27, 2013 12:22:11 AM UTC-5, Trendy Moniker wrote:
I'm still a bit confused on what best practices are for this code pattern.  The style guide says to avoid overly specific types, and not to use unnecessary static parameters.  At the same time, the parametric composite types entry suggests that there can be significant performance hits for declaring arrays of abstract types.  My initial impression had been that  a definition like function do_stuff2{T<:Integer}( m::Matrix{T} ) caused the compiler to create one version of the function for each concrete type under Integer, (e.g.: Int8, UInt8, Int16, etc.) but reading the parametric composite types entry and looking at methods(do_stuff2) makes me think that was wrong. 

You don't need a type declaration at all for performance.  You can just have

     function do_stuff2(m)
         ....
     end

and it will be just as fast.  When you call do_stuff2 with a particular type of matrix as an argument, Julia will compile a version of do_stuff2 specialized for that type.

The main reasons to specify a type in a function declaration are:

   * Dispatching: if you want to have different do_stuff(m) functions for different types m, then you need to declare types to indicate which functions are called for what types.  (This is the norm for heavily-overloaded functions like "sum" or "+".)

   * Correctness: if your function will behave badly if you pass the wrong type, e.g. a matrix of floats instead of integers, then you should specify the type to prevent errors.  (This is not generally necessary if passing the wrong type will merely cause an exception to be thrown, since adding a type declaration merely changes which exception is thrown.)

   * Clarity: sometimes the code is easier to read if the argument types are specified, to make it easier to understand what types the code expects.  However, I would be cautious about declaring types for this reason alone, as it is easy to overtype.

The main reason not to specify a type is generality.  If your code can work on both types A and B, why restrict it to just A?  The difficulty here is that you may not even realize that B exists when you write your code, since you may only have A in mind at the time.

If you are going to specify a matrix type, it is almost always a good idea to specify AbstractMatrix{T} in your declarations rather than Matrix{T}.  That way, your code will work on any "matrix-like" 2d-array object, even if the internal storage format is different from Matrix{T}.  (For example, a SubMatrix, or a PyArray matrix wrapping a NumPy matrix.)   There is no performance penalty to this: if you pass a Matrix{Int}, Julia will compile a specialized version of your function for that type.

Understanding when and why type declarations are needed is one of the most confusing parts of Julia, even for experienced developers.

--SGJ

Eric Davies

unread,
Nov 27, 2013, 10:00:07 AM11/27/13
to julia...@googlegroups.com
+1

This should augment the entries in the style guide IMO.

Jiahao Chen

unread,
Nov 27, 2013, 11:49:39 AM11/27/13
to julia...@googlegroups.com
> my apologies for the spam

No apologies needed. This was one of the first questions I had about Julia also. That this still shows up as a question says how much more documentation we need.

I won't speak for others, but Steven is right, understanding how type declarations are needed and used is still fuzzy for me.

James Porter

unread,
Nov 27, 2013, 3:36:05 PM11/27/13
to julia...@googlegroups.com
+1 for adding Steven's answer to the style guide; it's very helpful.
Reply all
Reply to author
Forward
0 new messages