When developing a package, what is the convention when it comes to type annotating a function’s arguments? For instance, say I have a function that takes in three arguments, like so:
function f(a, b, c)
@. a * b + c
end
and say that I always intend for a
to be a Vector
, and b
and c
to be real numbers. Then, should I type annotate the function signature like so:
function f(a::AbstractVector, b::Real, c::Real)
@. a + b * c
end
or should I only do so when I intend to write different methods for Vector
s and Matrix
s, and want to use multiple dispatch to do that? Does type annotating a function’s signature help documentation, or should it be only used for dispatching on a function’s arguments?
3 Likes
With libraries, I would generally lean towards Abstract annotations, or no annotations if nothing sufficiently abstract exists. I wouldn’t typically use a AbstractVector
annotation, because a lot of code that works with Vector
works with other collections like Set
, and there’s no way to annotate general Iterables. But I would use Number
, which is pretty general.
For applications, I’ll generally be a lot more specific, because the odds of someone wanting to use the code in a different way are much smaller, and the users of the code are my coworkers so it’s easier to change if needed.
Types are great documentation IMO, and definitely worth using that way in applications. My coworkers and I find it much easier to debug code with consistent annotations.
5 Likes
See also the discussion of argument-type declarations in the Julia manual, which discusses the utility (but not necessity) of declarations for clarity and correctness purposes, in addition to dispatch.
4 Likes
With regard to your question about docs: Documenter.jl doesn’t pick up type annotations as far as I’m aware – just whatever you write in your docstring. That said, of someone calls methods(f)
on your function, they will see it printed out with the type annotations.
And if they go to read your source code, it will help them understand the meaning of your arguments. That’s part of the reason I’m working on DuckTypes.jl. I want to be able to annotate arguments with their meaning without being bound to the type lattice.
2 Likes
That is how I have been using them till now, since using anything not sufficiently abstract will overspecialise the function. I am mostly writing libraries for now, hence I was not sure if using abstract annotations like AbstractVector
and Real
was alright or not. This clears it up a bit though. Thanks!
Thanks for sharing this @stevengj!I was a bit concerned about a prospective performance hit, but it is clearly written that there is no such hit in the docs:
Argument-type declarations normally have no impact on performance: regardless of what argument types (if any) are declared, Julia compiles a specialized version of the function for the actual argument types passed by the caller. For example, calling fib(1)
will trigger the compilation of specialized version of fib
optimized specifically for Int
arguments, which is then re-used if fib(7)
or fib(15)
are called.