RFC: TypedCallable
Introduction
This document proposes a new built-in closure object for efficiently calling a runtime-known function with compile-time known argument types, similar to a C function pointer (but for Julia code).
Users can construct a
TypedCallable{AT,RT}from any callable objectfand a set of argument + return types:global logger::TypedCallable{Tuple{String},Nothing} # declare type for type-stability logger = TypedCallable{Tuple{String},Nothing}(FileLogger()) # set logger dynamically logger("Test logging message") # calls `(::FileLogger)(::String)`Calling this object (e.g.
typed_callable(args...)) is then a fast equivalent of:invokelatest(f, (args::AT)...)::RTi.e. calling
fin the latest world with an implied set of argument and return type assertions.Unlike a typical "dynamic dispatch", calling a TypedCallable can be nearly as fast as a fully static function invocation. The fast execution path is an integer comparison + indirect jump via a function pointer.
It also infers well by default, without additional type assertions.
Full Proposal: https://hackmd.io/@pnwOFwoGSfuDnlKA1M6XaQ/RFC-Julia-TypedCallable
Differences vs. #55111
This is a follow-up to the (now defunct) https://github.com/JuliaLang/julia/pull/55111.
In comparison to that PR, TypedCallable is now a first-class object specially-managed by the Julia runtime. This new design:
- allows
TypedCallableto survive pre-compilation (without becoming aMissingCodeError/ segfault to call, in contrast toOpaqueClosure) - may not require the JIT at runtime (due to improved serialization support, in contrast to
FunctionWrappers) - has better code re-use in the runtime, so that a
TypedCallablecalls the "same" code that a normal call does
Status
Implementation is ongoing.
Proposal is shared here for feedback and to document the rationale behind the upcoming implementation.
Pleased to see progress on this.
This may be beyond the scope of the design, but would it be possible for method signatures to be part of the type system, such that you can use them for type constraints? At the moment we have Function, which a) says nothing about the methods of the function, and b) isn't applicable to all callables. It would be nice if you could say, for purposes of struct fields or method arguments, that a callable needs to conform to a method signature. This TypedCallable achieves that, but it incurs with it the runtime dispatch overhead.
It would be nice if you could say, for purposes of struct fields or method arguments, that a callable needs to conform to a method signature. This TypedCallable achieves that, but it incurs with it the runtime dispatch overhead.
Can you share an example?
Typically some amount of dispatch overhead (in this case, a function pointer) is unavoidable, unless you know the identity of the Function object which you can already write as ::typeof(foo) today.
Can you share an example?
Typically some amount of dispatch overhead (in this case, a function pointer) is unavoidable, unless you know the identity of the
Functionobject which you can already write as::typeof(foo)today.
Sure, in our systems we often have code in the forms:
function f(g, ...)
...
g(...)
...
end
or
struct X{H1, H2, ...}
h1::H1
h2::H2
...
end
function f(x::X, y)
...
if some_condition
x.h1(y)
elseif some_other_condition
x.h2(y)
end
...
end
We build event driven systems where you may have chains of handlers from some root event (e.g., a network packet), and we maintain specific types for all callbacks up the chain, with only type erased functions where we need them (e.g., at the root). It would be a nice feature for us if we could specify the signature for methods these callables have, without wrapping them in an type erased layer like TypedCallable, which requires indirection. It feels to me if the expressive power is there to describe method signatures in types, it would be elegant if we had a type constraint we could apply to say a callable type must provide a method of a signature. I know you could do this with explicit checks to see if methods exists, and I realize since the only way to type constrain currently is through a type hierarchy, the closest we can do is probably using one of the traits implementations. Only musings, I'm sure this falls out of scope of the TypedCallable feature.
I'm sure this falls out of scope of the TypedCallable feature.
Yeah, this proposal is concerned with performing a fast partially type-erased call - interface proposals are a separate category
Is it something like std::function in C++?