Specialisation rules for function objects and type parameters

The Julia docs say:

I am wondering if this applies recursively or not? For example,

struct Wrapper{F<:Function}
    f::F
end
(w::Wrapper{F})(x) where {F} = w.f(x)

f_func(f, num) = ntuple(f, div(num, 2))

Would f_func specialise to the type parameter of Wrapper? For example if I write

f_func(Wrapper(sin), 5)

would f_func specialise to Wrapper{typeof(sin)} even though it does not specialise to typeof(sin) itself?

Furthermore, would it change if I declare Wrapper{F} <: Function?

I think I might have just answered my own question. It will specialize to Wrapper{typeof(sin)}, but not if Wrapper <: Function:

julia> struct Wrapper{F<:Function}
           f::F
       end

julia> struct Wrapper2{F<:Function} <: Function
           f::F
       end

julia> (w::Wrapper{F})(x) where {F} = w.f(x)

julia> (w::Wrapper2{F})(x) where {F} = w.f(x)

julia> f_func(f, num) = ntuple(f, div(num, 2))

julia> f_func(sin, 5); f_func(Wrapper(sin), 5); f_func(Wrapper2(sin), 5)
(0.8414709848078965, 0.9092974268256817)

julia> Base.specializations(@which f_func(sin, 5))
Base.MethodSpecializations(svec(MethodInstance for f_func(::Wrapper{typeof(sin)}, ::Int64), MethodInstance for f_func(::Function, ::Int64), nothing, nothing, nothing, nothing, nothing))

So we can see there is f_func(::Wrapper{typeof(sin)}, ::Int64). But since Wrapper2 <: Function, it results in no specialisation!

1 Like

This is exactly as described in the manual. The compiler just mechanically skips specializations for Type, Function, and Vararg. Eg in the following example, the type is not even callable, but since it is <:Function, the rule still applies:

struct Foo{T} <: Function
    x::T
end

bar(f::Foo) = f.x # ain' t calling anything

f = Foo(2)

bar(f)

Base.specializations(@which bar(f)) # empty

But of the 3 exceptions above, you can only do this trick with Function, as the compiler complains if you try to subtype Vararg and Type.

2 Likes