Using `Core.Inference.return_type`

I discovered this function in 0.6 (EDIT: which apparently existed in 0.5 as well) and it addresses a task I have come across a couple of times in the past. Often I would like to query the return type of a specific method (for which I know all the parameter types already) without executing it.

It seems to work perfectly, but I wonder if using it is discouraged for some reason. As far as I can tell from the documentation the one place explicitly discouraged is within generated functions.

Any other gotcha?

1 Like

I think it’s fine. @stevengj suggested I use that function for some code: https://github.com/ChrisRackauckas/VectorizedRoutines.jl/blob/master/src/julia.jl#L44 , I’d never heard about it before but it seems very useful to me.

1 Like

AFAIK it may not infer the narrowest type (though it does pretty well in practice).

It’s somewhat problematic to have the behavior of your code depend upon compiler internals (whether inference succeeds), so we try to avoid using return_type if there is another way to do things.

4 Likes

Well, after playing around with it, it seems it doesn’t quite work for what I have had in mind anyway. Or at least I was not able to make it work. While it returns the correct type for me as a value, it doesn’t infer it (@code_warntype shows it always infers Any). Inference is what I really am after.

My silly workaround so far is T = typeof(f(a,b)) in various places, which computes the result of f(a,b) even though all I need is the type. This feels wasteful, but it works and infers correctly. Is there no better way to do this?

This is actually a pretty good way to do it if you

  • have canonical values for a and b that you know the function accepts and does not throw an error on
  • know the function is type stable (although not necessarily type inferrable)
  • the function has no side effects

The computation is not done (i.e. optimized out) if the function has no side effects, is type stable, does not depend on any global state, and the compiler is able to figure all these facts out. Providing the Base.@pure annotation may be necessary for all this to be true.

1 Like

To be a little more concrete about the use case.

I have various situations where I create a custom subtype of AbstractArray, where getindex always calls the same inferable function. So naturally I would like to set the eltype of my custom array to the correct value, which depends on some other input.

A simplified version of the code looks something like this. This example is kinda like a MappedArray, but that is not always the case:

immutable ObsView{TElem,TData} <: AbstractArray{TElem,1}
    data::TData
end

function ObsView{T}(data::T)
    E = typeof(datasubset(data, 1))
    ObsView{E,T}(data)
end

Base.getindex(A::ObsView, i::Int) = datasubset(A.data, i)

So basically I would like to avoid executing datasubset(data, 1) in the constructor, without compromising type inference

Understood. Thank you. I’ll keep it this way then for the time being

Sounds like I could use that syntax for the snip I linked above too, maybe an idea if this is a more stable implementation.

I had basically the same questions at some point:

In my case it had to do with allocating an array with unknown but concrete type in situations where map cannot be used or would lead to hard to read code.

@fengyang.wang, is not having a side effect a hard requirement on the function?

In general I think it would be good if these topics and the use of @pure would be discussed in the Julia documentation. It is central to Julia being a dynamically typed language: the advantages that come with it such as runtime multiple dispatch, and the disadvantages such as the absence of a decltype construct like c++ has, leading to awkward code relying on non-exported functions or compiler optimisations as is the case here…

1 Like