Do you put return type in function definitions?

Do you have any opinion about annotating function definitions with return types? Like it or not and why?

Here’s a dummy example:

add(x, y)::Int = x + y

It gives additional type checking:

julia> add(1,2)
3

julia> add(1.0,2)
3

julia> add(1.1, 2)
ERROR: InexactError: Int64(3.1)
2 Likes

In general, don’t. The fact that methods in julia can automatically infer return types is a feature, not a bug.

3 Likes

I agree that it’s a feature. The question is whether it’s “idiomatic” to annotate return type and make Julia do type checks at runtime.

1 Like

No it’s not. There are cases where return type annotation is useful. However, you must not take that as a license to use it for all or most of your functions.

It also has little to do with runtime type check…

3 Likes

FWIW, I sometimes find it helpful to annotate return types in function definitions if I’m using several function calls within the same function but this is mostly for clarity for own own sake when I come back to large projects after a few weeks or months.

I don’t think performance is affected by whether you annotate return type in a function or not, but correct me if I’m wrong.

1 Like

I try to write mostly generic code so I never do this.

The only thing I ever use is its twin brother some_function_call(x, y)::SomeType to give the compiler a nudge for inference, but that’s an entirely different thing.

4 Likes

In general I don’t do it, as I use Julia as a dynamic language.
The same reason, why I don’t do it neither (in general) for the function parameters.

Thats how I start. And during evolving of the code I think here or there I should specify the types.

1 Like

Do I understand this right?
You know that some_function_call returns SomeType, but you help the compiler with specifiying it explicitely in the line of calling some_function_call?

Do you have an example on which I can see why this can be?

1 Like

Type inference can fail in some complex settings, but this usually happens in nontrivial code so I don’t have a self-contained MWE to demonstrate. The last time I needed this was a

mapreduce(some_complicated_function::AbstractVector, hcat, xs)::AbstractMatrix

because inference was shaky (it was a Union of a few things). This was in one of the release candidates for 1.3 so I don’t know if it persists, also it was under many layers in a 10k LOC codebase.

2 Likes

I expected this, but your example is a good one. I didn’t imagine, when I asked, that functions e.g. can be parameters as well and the return type can change with that and adding some complexity does the rest.

Sorry, this is nonsensical, it was more like

mapreduce(x -> some_complicated_function(x)::AbstractVector, hcat, xs)::AbstractMatrix
3 Likes

I think in general it is nice to see the return type in the place where the function is defined (i. e. at the top). Much better than having to hunt for it through the source code, emulating the work of the compiler really. For instance, what does this function return

inv(B::BunchKaufman{<:BlasComplex})

on line 350 of bunchkaufman.jl? Wouldn’t it be nice if this information was on the line where this function is defined?

4 Likes

For a lot of high-level idiomatic Julia code, the return type may be just be an implementation detail, and/or depend on the input types in a way that is impractical to document with ::. Eg

"""
    make_hermitian(A)

Return a hermitian matrix that is similar to `A` in size and type.

For unit testing.
"""
make_hermitian(A) = Hermitian(A' * A)

make_hermitian(rand(10, 10))

make_hermitian(@SMatrix rand(10, 10))

True.

Note that Julia does not dispatch over return type as may be desired when searching this topic.

1 Like