Skip to content

Instantly share code, notes, and snippets.

@tkf
Last active June 7, 2020 22:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tkf/e10e3dd3349a25c858080c91799aa88a to your computer and use it in GitHub Desktop.
Save tkf/e10e3dd3349a25c858080c91799aa88a to your computer and use it in GitHub Desktop.
Base.return_type(f, T::Type{<:Tuple})

Infer a possible return type of f with input argument types T.

!!! warning

Using `Base.return_type` without understanding the API is likely to cause
undefined behavior.

Base.return_type returns a type R such that every return value y of f(args...) for any args <: T satisfies y isa R. Note that R is not guaranteed to be the tightest type with such property.

The API implemented using Base.return_type MUST return the result that does not depend on the inferred type R. That is to say, the behavior of Base.return_type must not be observable by the caller of the API within the gurantee provided by the API.

Example

Following function is an invalid use-case of Base.return_type.

function invalid_usecase1(f, xs)
    R = Base.return_type(f, Tuple{eltype(xs)})
    ys = similar(xs, R)
    ys .= f.(xs)
    return ys
end

This is because the value the caller get by eltype(ys) depends on exactly what Base.return_type returns. It may be fixed by re-computing the element type before returning the result.

function valid_usecase1(f, xs)
    R = Base.return_type(f, Tuple{eltype(xs)})
    ys = similar(xs, R)
    ys .= f.(xs)
    S = mapfoldl(typeof, Base.promote_typejoin, ys; init = Union{})
    if S != R
        zs = similar(xs, S)
        copyto!(zs, ys)
        return zs
    end
    return ys
end

Note that using isconcretetype is not enough to safely use Base.return_type. Following function is another invalid use-case of Base.return_type.

function invalid_usecase2(f, xs)
    R = Base.return_type(f, Tuple{eltype(xs)})
    if isconcretetype(R)
        ys = similar(xs, R)
    else
        ys = similar(xs, Any)
    end
    ys .= f.(xs)
    return ys
end

This is because whether or not the caller gets Any element type depends on if Base.return_type can infer a concrete return type of the given function. A fix similar to valid_usecase1 can be used.

Another possible fix for invalid_usecase1 and invalid_usecase2 is to clarify the API guarantee:

another_valid_usecase1(f, xs::AbstractArray) -> ys

Return an array ys such that every element in xs with the same index is mapped with f.

The element type of ys is undefined. It must not be used with generic functions whose behavior depend on the element type of ys.

However, it is discouraged to define such unconventional API guarantee.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment