Creating the instance of a function type

The type of a standard Julia function f is typeof(f), which is a singleton.

julia> function f end
f (generic function with 0 methods)

julia> typeof(f)
typeof(f) (singleton type of function f, subtype of Function)

But it is not possible to recover the type’s sole instance with a constructor.

julia> typeof(f)()
ERROR: MethodError: no method matching typeof(f)()
The type `typeof(f)` exists, but no method is defined for this combination of argument types when trying to construct it.
Stacktrace:
 [1] top-level scope
   @ REPL[6]:1

Is there any way to do this?

Not sure what you need this for, but here you go :slight_smile:

julia> function f end
f (generic function with 0 methods)

julia> typeof(f).instance
f (generic function with 0 methods)

julia> typeof(f).instance()
ERROR: MethodError: no method matching f()
The function `f` exists, but no method is defined for this combination of argument types.
Stacktrace:
 [1] top-level scope
   @ REPL[3]:1

julia> f()=2
f (generic function with 1 method)

julia> typeof(f).instance
f (generic function with 1 method)

julia> typeof(f).instance()
2

Note the subtle difference in the errors (typeof(f) vs f).

1 Like

Thanks - I was trying to build a data structure that represented the lazy application of a function, but I realized that it wouldn’t make much sense to do it the way I originally envisioned. At this point I’m more so interested out of curiosity.

I’m left with two questions now:

  1. Is there a way to do this that doesn’t depend on using internals of the function type?
  2. If not, is there some reason why constructing a function from its type is a bad idea?

This thread seems to have some clues, and I would heed the advice rather than relying on internals…

2 Likes

No, because it might not exist! For example, Returns is a Function, but this doesn’t work:

julia> f = Returns(3)
Returns{Int64}(3)

julia> typeof(f)
Returns{Int64}

julia> f isa Function
true

julia> typeof(f).instance
ERROR: UndefRefError: access to undefined reference
3 Likes

Instead of

typeof(f)()

just use

f

The object f is the singleton object with type typeof(f) !

The OP started out by talking about

which Returns is not; Returns is a struct that subtypes Function. So, I was wondering if there is an answer to the questions

that applies to standard Julia functions specifically (besides the usual don’t rely on internals because they are not public API).

I personally think @CameronBieganek’s answer is the best answer: there is no need to use typeof(f) in the first place because f is a first-class object and can be used directly.

What’s a “standard Julia function?” I’m not sure I know. What about this Returns?

julia> f = Returns(nothing)
Returns{Nothing}(nothing)

julia> typeof(f).instance # it works!
Returns{Nothing}(nothing)

Why should that work? Or what about a closure?

julia> printer(x) = ()->println(x)
printer (generic function with 1 method)

julia> f = printer(nothing)
#printer##0 (generic function with 1 method)

julia> typeof(f).instance # it works!
#printer##0 (generic function with 1 method)

julia> f = printer(3)
#printer##0 (generic function with 1 method)

julia> typeof(f).instance # it doesn't!
ERROR: UndefRefError: access to undefined reference

Or what about constructors? They’re callable, too.

4 Likes

The reason I asked this was in the case of something like

struct Foo{F<:Function}
end

you could not work with the stored function by instantiating it like a normal concrete type:

function bar(baz, ::Foo{F}) where F
    f = F()
    return f(baz)
end

Instead you’d have to do something along the lines of

struct Foo{F<:Function}
    f::F
end

function bar(baz, foo::Foo) 
    return foo.f(baz)
end

I was originally considering a design like this but realized it wasn’t a good idea for many reasons - in part because f may or may not have methods that take a single argument.

julia> sizeof(sin)
0

julia> sizeof(Returns(nothing))
0

julia> sizeof(Returns(3))
8

In most cases where the function is a singleton that can be retrieved from .instance, it also has sizeof(F) == 0. So while it does take up a “field” in the struct, it does not require any memory. In other words, there is no waste in using the struct Foo{F}; f::F; end pattern. The advantage of this format is that it also works on closures, callable structs, etc that may require additional memory.

4 Likes

I was defining “standard Julia functions” as those defined with the function keyword (or the shortened, math-like syntax) in the top-level scope (not an inner function, not a closure, not an anonymous function). But even that definition is more nuanced than I originally thought because it includes constructors. I guess I could further restrict my definition to exclude constructors, but I think constructors are pretty “standard”. So, yeah, I see your point. I guess I was mainly wanting to provide some nuance to the conversation because I personally wouldn’t consider a callable struct a “standard Julia function”.

Probably they mean a global function that you define with function foo(...) .... or equivalent, which is a singleton subtype of Function.

See also this thread: How to get a function instance from a function type? - #2 by Mason or this thread: Instantiating function from type? - #4 by nsajko

1 Like

Limiting ourselves to globally scoped function definitions is probably too much. We can check the public Base.issingletontype directly before attempting to access the internal .instance cache.

This is unfeasible because Function subtypes are sometimes not singleton and usually intentionally remove the constructor when it is singleton. I’d say store the function as a parameter, you can do that for any isbits instance so “empty” singleton instances count. You just can’t annotate it like array dimensions can’t be annotated with Int, but you could enforce it beforehand.

julia> struct Foo{f#=isa Function=#} end

julia> Foo(f::Function) = Foo{f}()
Foo

julia> bar(baz, ::Foo{f}) where f = f(baz)
bar (generic function with 1 method)

julia> bar(25.01, Foo(sqrt))
5.000999900019995

The “advantage” over storing it as a possibly “empty” field f::F is that you still have an “empty” singleton Foo even when f’s type is not (the aforementioned closures and callable non-empty structs), though a field can store non-isbits instances. The choice of non-singleton support is probably down to the exact use case.

julia> (function(x); x, sizeof(x) end)(Returns(3))
(Returns{Int64}(3), 8)

julia> (function(x); x, sizeof(x) end)(Foo(Returns(3)))
(Foo{Returns{Int64}(3)}(), 0)

I will link Jameson’s important comment from that thread, and emphasize below:

What is wrong with the solution he presented, where you store the function in the struct and access it by value as above, why does it need to be done in the type domain?

3 Likes