Meaning of {T}

What is the meaning of the first {T} in this function declaration?
Is it a return type?

function zca{T}(o::Whiten{T})
    y = ....

Newbie, coming from numpy.

1 Like

Welcome!

From Parametric Types in the manual (although the question pertains more to Parametric Methods):

it can be any type at all (or a value of any bits type)

It means that function zca can have a family of methods, like:

function zca{Int32}(o::Whiten{Int32})
function zca{Float64}(o::Whiten{Float64})
...

Note: The above code is not meant to be copied by users, it is meant for demonstration purposes only.

That (the usage of {T}) advises the compiler to produce separate code for each method (when a different parameter gets actually used), resulting in faster code. It is easier to understand that, if you think of built-in arrays and dictionaries. They are parametric types declared as Array{T,N} and Dict{K,V}, where T, N, K and V initially don’t make sense, but the declarations actually mean Array{elements_type, nof_dimensions} and Dict{keys_type, values_type}, allowing the same names to be used for arrays of any dimension and combinations of any key-value pairs, without sacrificing performance. The names are kept short for convenience and the exact meaning is left to the way they are used by the rest of the code.

Return types are usually inferred by the compiler. If someone has to provide them, they go right after the parentheses. In your example, it would be like this:

function zca{T}(o::Whiten{T})::T
2 Likes

More concretely, the stuff that appears within those {} brackets in function definition syntax f{…}(…) define the names that are used as TypeVars in the argument list that follows. TypeVars are kinda like wildcards; they try to match against the arguments that you pass. Those names are then bound to what they match — and you can use them within the function body. You can also restrict the TypeVars to be subtypes of an abstract type within those curly brackets.

julia> f{T}(A::Vector{T}) = "eltype: $T"
       f{T<:Number}(A::Vector{T}) = "number eltype: $T"
f (generic function with 2 methods)

julia> f([:a, :b, :c])
"eltype: Symbol"

julia> f([1,2,3])
"number eltype: Int64"

While TypeVars are often used in conjunction with parametric types (like how I used Vector{T} above), the curly brackets in those two contexts have very different meanings. The syntax Vector{T} needs T to already be bound — either to a TypeVar that was introduced in a function definition or bound directly to a type.

7 Likes

Note that this is the case even if f is defined as:

function zca(o::Whiten)

I’m not exactly sure what the advantage is of the {T} notation, other than getting easy access to the type.

That’s not true. Julia will always specialize functions concrete types. In fact,

That is very ill-advised. You likely just want to do zca(o) = ... since there is no performance gain to restricting it, and this kind of duck-typing allows users to find new maybe unintended uses (i.e. maybe it works on a “non-traditional number” like a ApproxFun Fun, so maybe in this case you shouldn’t even T<:Number and should instead leave it open!). Strict typing of functions is kind of a new user trap for this reason: it’s just for throwing errors, but has a feel that it might improve performance.

On the other hand, strictly typing for type fields DOES lead to better performance. So:

type Test
  a
  b
end

is bad (check @code_warntype on something that uses the fields), while

type Test{A,B}
  a::A
  b::B
end

is good. That’s why there’s the mantra: strictly type your types, loosely type your functions.

So then why the extra notation for type variables? Well, two reasons. The first is for matching types.

f{T<:Number}(a::T,b::T)

is different from

f(a::Number,b::Number)

because it forces a and b to not only be Numbers, but the same subtype of Number. This restriction is for dispatch, not for performance. Lots of times in this case you’d have a different dispatch promote the types to something compatible, which would then call this “same number type” dispatch.

Secondly, within this function we already know T. This can be handy. Of course, you can likely just use typeof(a) (and this will likely be a no-op, i.e. it will also be inferred at compile time so there’s no extra function evaluation cost) but this still might be handy.

So it’s about dispatch control, not performance (when on a function).

4 Likes

And just a quick aside (since you’re referring to my code)… in this specific case the brackets were unnecessary, but the function just above uses the type T in the body of the code to ensure type stability of the constant:

function whitened_pca{T}(o::Whiten{T})
    y = normal_pca(o)
    y[:] = y ./ sqrt.(o.e .+ T(1e-8))
    y
end
3 Likes

17 posts were split to a new topic: Digression – was: Meaning of {T}

How about

function whitened_pca(o::Whiten)
    y = normal_pca(o)
    y[:] = y ./ sqrt.(o.e .+ 1//100_000_000)
    y
end

? This is better in the case T==BigFloat, but may be worse performance-wise.