On a 64-bit platform, Int is just another name for Int64. There is no Float. However, often the types can be inferred and don’t need to be annotated at all.
If I want to enforce say integers in a function call, is it better to restrict the types on which the function can be called, or to throw an particular error:
option 1:
function f1(n::Integer) = n + 1
option 2:
function f2(n)
typeof(n) <: Integer || throw(error("error"))
n + 1
end
Almost never, and almost certainly not in the cases you’re thinking of.
On 32 bit platforms, Int refers to Int32. On 64 bit platforms, Int refers to Int64. There is no equivalent shorthand for floating point numbers.
For variables, type assertions limit the type of that binding in the current scope to the asserted type.
For function arguments in function definitions, type assertions restrict the allowed incoming types for that argument.
For struct fields, type assertions signify that the field in question holds a value of the asserted type. The default constructors will try to convert incoming arguments to the types in question.
The answer strongly depends on the context the type annotation is used in.
Function signatures
Annotating argument type makes no performance difference, but adds dispatch methods. The general recommendation is to use concrete types in function signatures only for dispatch, and use a possibly loose type bounds otherwise to keep the functions generic. In translating code from Fortran, I’d recommend using Integer and Real type annotations for the beginning to easier keep track what is what.
Variable declaration within functions
Usually type declaration is redundant but you need to watch for type stability for performance. Sometimes, a nudge to the type inference algorithm may be useful, though. For performance, the types must be concrete or small unions.
Structure type members
Concrete types are absolutely crucial for performance.
Type parameters / generics
The advises on how to annotate function argument types and local variable types are contradictory. So, you may go without any type annotations for local variables (what’s usually done anyways) or, if you feel uneasy about it, you could use types as parameters in signatures and function body (if you continue using Julia, you’ll need to learn it in any case, so this is just one of many examples)
# Bad: input type is too tight
function cube(x::Float64)
local square::Float64 = x * x
return square * x
end
# Better but local variable type is too loose.
# Does not help with type instabilities within the abstract type
function cube(x::Number)
local square::Number = x * x
return square * x
end
# Good: concrete type is parameterized.
# Function is now generic, and all local variable types are concrete.
# A drawback, of course, is that {T<:Number} is also too tight
# (it could include matrices, rotations, etc)
function cube(x::T) where {T<:Number}
local square::T = x * x
return square * x
end
Turns out, abstract type annotations don’t stop the compiler from narrowing down the type, so it must be the same as untyped version performance-wise.
But it won’t catch type instabilities or inefficiencies within the abstract type annotation, if that was the intent.
I’ll edit my post accordingly, thanks for pointing that out.