Is there a way to get f from typeof(f)?


#1

I’m looking for somemagicfunction which behaves like

julia> somemagicfunction(::Type{typeof(one)}) = one
somemagicfunction (generic function with 1 method)

julia> somemagicfunction(typeof(one))
one (generic function with 12 methods)

but for arbitrary function (and without defining it manually as above).

Obviously I tried:

julia> typeof(one)()
ERROR: MethodError: no method matching typeof(one)()

FYI, the use-case is to use the function (object) in a trait like this:

julia> struct MyStruct{F}
           f::F
       end

julia> ExtractFun(::Type{MyStruct{F}}) where F = somemagicfunction(F)
ExtractFun (generic function with 1 method)

julia> ExtractFun(typeof(MyStruct(one)))
one (generic function with 12 methods)

I know that I can do Val{one}() so maybe that’s the way to go. But keeping function in the field is very handy. I can put it both in the type parameter and the field but it feels redundant.


#2
julia> struct MyStruct{F}
           f::F
       end

julia> foo(::MyStruct{F}, x) where F = F.instance(x)
foo (generic function with 2 methods)

julia> myabs2 = MyStruct(abs2)
MyStruct{typeof(abs2)}(abs2)

julia> foo(myabs2, 3.0)
9.0

julia> @code_warntype foo(myabs2, 3.0)
Body::Float64
1 ─ %1 = (Base.mul_float)(x, x)::Float64
└──      return %1

EDIT:
For those more interested in fishing than fish, I found the solution via

julia> foo(::MyStruct{F}) where F = F
foo (generic function with 2 methods)

julia> ta = foo(msabs2)
typeof(abs2)

julia> ta |> dump # normally a good first try for understanding Julia objects
typeof(abs2) <: Function

julia> ta |> typeof |> fieldnames # when that fails...
(:name, :super, :parameters, :types, :names, :instance, :layout, :size, :ninitialized, :uid, :abstract, :mutable, :hasfreetypevars, :isconcretetype, :isdispatchtuple, :isbitstype, :zeroinit, :isinlinealloc, Symbol("llvm::StructType"), Symbol("llvm::DIType"))

Then it is only a matter of looking at the fields and finding which one you want:

julia> ta.instance
abs2 (generic function with 4 methods)

EDIT:
Please see jameson’s comment:

Most of the “solutions” presented here are wrong: you should never need to access “.instance”. This is an internal API and not stable. The correct way to do this is to store the actual function object itself, and always refer to it by value and don’t try to do this in the type-domain. Adopting the original example, we get the much-shortened:

julia> struct MyStruct{F}
         f::F
     end

julia> ExtractFun(s::MyStruct) = s.f

Note that (IIUC), this means that code using what I wrote above can break between Julia 1.0 versions.


#3

Thanks! I have no idea why I didn’t try fishing :slight_smile:


#5

Note that non-singleton types have an undefined instance field. One can test for this with Base.issingletontype instead of using isdefined directly.

Perhaps Base.instances could provide an instance of all singleton types, including functions.


#6

Here is another approach, if you want it to return an actual function

julia> struct MyStruct{F}; f::F end

julia> some_magic(::MyStruct{F}) where F = x -> F.instance(x)
some_magic (generic function with 1 method)

julia> g = MyStruct{typeof(one)}(one)
MyStruct{typeof(one)}(one)

julia> some_magic(g)(Float64)
1.0

#7
some_magic(::MyStruct{F}) where F = F.instance

is equivalent. In general, use f instead of x -> f(x). See also the style guide.


#8

Yes, I was going to edit my comment, but was prevented because the website was disabled for a few


#9

Apparently, it’s not needed to store the Function as a field in the type

julia> struct hey{N} end

julia> fun(::hey{N}) where N = N.instance
fun (generic function with 1 method)

julia> fun(hey{typeof(x->x^2)}())
#4 (generic function with 1 method)

julia> ans(2)
4

the anonymous function was never stored anywhere, only its type was stored, and it can be re-called


#10

Or just

julia> some_magic(::Type{f}) where f = f.instance
julia> some_magic(typeof(zero))
zero (generic function with 14 methods)
julia> some_magic(typeof(x->x^2))
#3 (generic function with 1 method)

#11

typeof(f).instance is right. This should work with any object which has no fields.

Note that it doesn’t work for nontrivial closures though because they contain state:

julia> capture_value(g) = ()->g
capture_value (generic function with 1 method)

julia> f = capture_value([1,2])
#19 (generic function with 1 method)

julia> f()
2-element Array{Int64,1}:
 1
 2

julia> typeof(f).instance
ERROR: UndefRefError: access to undefined reference
Stacktrace:
 [1] getproperty(::Type, ::Symbol) at ./sysimg.jl:15

#12

Note that for anyone reading through this statement is false. Functions are not always singleton types, and are thus should not be assumed to be a subset of “all singleton types”. In fact, all objects are can be used as function arguments, so Any object may be given there.

Most of the “solutions” presented here are wrong: you should never need to access “.instance”. This is an internal API and not stable. The correct way to do this is to store the actual function object itself, and always refer to it by value and don’t try to do this in the type-domain. Adopting the original example, we get the much-shortened:

julia> struct MyStruct{F}
           f::F
       end

julia> ExtractFun(s::MyStruct) = s.f

Mutable struct vs closure
#13

Here is a performance comparison:

struct NoField{F} end
get_instance(::NoField{F}) where F = F.instance
g = get_instance(NoField{typeof(x->x^2+1)}())

and

struct WithField{F}
    f::F
    WithField(f) where F = new{typeof(f)}(f)
end
get_field(f::WithField{F}) where F = f.f
f = get_field(WithField(x->x^2+1))

comparison:

julia> @btime g(2.0)
  13.998 ns (1 allocation: 16 bytes)
5.0

julia> @btime f(2.0)
  15.286 ns (1 allocation: 16 bytes)
5.0

running it a second time

julia> @btime g(2.0)
  14.466 ns (1 allocation: 16 bytes)
5.0

julia> @btime f(2.0)
  13.676 ns (1 allocation: 16 bytes)
5.0

So in terms of performance, it’s about the same.


#14

Both f and g are non-constant globals here. Also I’m also not sure what this is supposed to show. If you just do

foo = x->x^2+1
g = get_instance(NoField{typeof(foo)}())
f = get_field(WithField(foo))

then f === g, so any performance difference is necessarily noise.


#15

The point is that the only reason why you might want to choose one over the other is due to the API reasons.


#16

However, there is a difference in performance for the struct itself

julia> @btime NoField{typeof($foo)}()
  0.026 ns (0 allocations: 0 bytes)
NoField{getfield(Main, Symbol("##3#4"))}()

julia> @btime WithField($foo)
  206.406 ns (0 allocations: 0 bytes)
WithField{getfield(Main, Symbol("##3#4"))}(getfield(Main, Symbol("##3#4"))())

#17

This seems to be an artifact of your inner constructor in WithField, although I’m not entirely sure why it’s happening. A slight modification results in identical performance (in fact, performance which is fast enough that it seems likely that the entire computation has been elided by the compiler):

julia> struct WithField{F}
           f::F
       end

julia> struct NoField{F} end

julia> function withfield(f)
         WithField(f)
       end
withfield (generic function with 1 method)

julia> function nofield(f)
         NoField{typeof(f)}()
       end

julia> f = x -> x + 1
#3 (generic function with 1 method)

julia> @btime nofield($f)
  0.017 ns (0 allocations: 0 bytes)
NoField{getfield(Main, Symbol("##3#4"))}()

julia> @btime withfield($f)
  0.016 ns (0 allocations: 0 bytes)
WithField{getfield(Main, Symbol("##3#4"))}(getfield(Main, Symbol("##3#4"))())

#18

Might be https://github.com/JuliaLang/julia/issues/29393 / https://github.com/JuliaLang/julia/issues/27813#issuecomment-400620048?

Also fixed by using f::F instead of just f as the constructor argument, so that the type parameter is used.


#19

Good advice. Using .instance will work in certain circumstances (and with certain julia versions) but it’s almost certainly a mistake to use it in real code. I just enjoy nasty hacks too much sometimes :slight_smile: