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
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).
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:
Is there a way to do this that doesn’t depend on using internals of the function type?
If not, is there some reason why constructing a function from its type is a bad idea?
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
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.
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.
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.
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”.
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.
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?