Following the discussion generated by https://github.com/JuliaLang/julia/pull/19801, I think that the world-capturing behavior of that proposal was probably sub-par (complex to implement and use). I think the following is instead a much more useful model. For those following along with that issue, this is also intended to be a very close model for how I think the cfunction
itself should also behave.
# a Callback is just a callable (plus a dispatch cache)
immutable Callback{ReturnType <: Type} <: Function
func::Any
cache::Ref{SimpleVector}
function Callback(func::ANY)
return new(func, Ref(svec()))
end
end
# allow implicit conversion of Any -> Callback{T}
# and Callback{T} -> Callback{S} where S >: T
eltype{RT}(::Type{Callback{RT}}) = RT
convert{C <: Callback}(::Type{C}, x::ANY) = C(x)
convert{C <: Callback}(::Type{C}, x::Callback) =
if (eltype(x) <: eltype(C))
return C(x)
else
throw(TypeError(:Callback, "convert", eltype(C), eltype(x)))
end
function (at::Callback{RT})(args...) where RT
# this implementation is pseudo-code,
# it would be actually implemented as an intrinsic
# since in general I think this can be inlined and
# optimized very effectively
push(tls-world-age = world-counter)
# during the following dispatch,
# the lookup result may get cached
# as `svec(Method, argtypes...)`
# for faster results
local retval = at.func(args...)::RT
pop(tls-world-age)
return retval
end
I find this to be much simpler in all of concept, implementation, and usage. This also serves to wrap an arbitrary object in a callable, and has optimization behavior figured out by the callee (rather than needing to be declared by the user).
This makes the above examples much simpler:
# type SomeType; callback::Callback{Any}; end
register_callback(func::ANY) = (this.callback = func; nothing)
do_callback(this::SomeType, args...) = this.callback(args...)
functions = Any[+, -, *, /]
arrows = [ Callback{Float64}(f) for f in functions ]
[ arrow(1.0, 2) for arrow in arrows ] # :: Vector{Float64}
function eval_something_newworld(func, args...)
# return eval(current_module(), Expr(:body, Expr(:return, Expr(:call, QuoteNode.(args)...))))
return Callback{Any}(func)(args...)
end
(When called from inside a generated functions, they would still work, they’ll just fallback to acting like a normal dynamic dispatch, since for the generated functions, the newest world is the world-age in which it was defined.)