While Core.Compiler.return_type
can give imprecise results, it is quite handy from time to time. Today my question is not about its usage, however about why the first argument - the function - is different compared to the rest.
All other arguments are given by type, but the function is given by instance.
Is there an underlying reason for this, or is it just an happenstance and underlying there is its counterpart, which takes everything by type without any drawback.
EDIT:
using typeof
and apply(f, args...; kwargs...) = f(args...; kwargs...)
I can get a homogenous version of return_type
.
return_type(FullTuple) = Core.Compiler.return_type(apply, FullTuple)
The core of my question is more whether this has any known disadvantages, or whether you really should use the function instance instead of the function type.
I am almost sure that someone understands it better and could give you a better answer, but I would guess that is because: (1) the type of a function and the function itself while different, are a 1-to-1 match, each function has its own single type, so they are kinda interchangeable; (2) it is possible that Core.Compiler.return_type
either do some kind of partial evaluation of the function, or call some other function that was also defined for the function itself (not its type) so it was just more convenient to receive the function too; (3) the reason Core.Compiler.return_type
gives imprecise types is probably because it only looks if it can state which is the return type of the method of the function that takes arguments of such types, the type of return can vary with the values passed (but to know this you have to basically evaluate the code), but you can already have an idea if you know the method that will be called only return values of some type, so Core.Compiler.return_type
is already making clear in its signature that it only look at parameter types not how the values can affect the return type.
2 Likes
thanks a lot, I made an EDIT to my question to clarify my focus.
I am not sure why you expect that it would be similar to the other arguments — the function plays a special role there.
Also, using a type would be somewhat cumbersome, with typeof(f)
etc. In any case, either one identifies the function.
2 Likes
thanks.
sometimes you may only have the function type available and need to fallback to using something like apply
. Then it would be great to reuse the same machinery as for functioninstances.
is the use of apply
the most basic you can get, or is there another function in Core which can be used for this?
apply(f, args...; kwargs...) = f(args...; kwargs...)
return_type(FullTuple) = Core.Compiler.return_type(apply, FullTuple)
ulia> f() = 1
f (generic function with 1 method)
julia> F = typeof(f)
typeof(f)
julia> Core.Compiler.singleton_type(F) === f
true
3 Likes
really nice.
Caveat: This does not work for singleton Types
Core.Compiler.singleton_type(Type{Dict})
and probably also not for other callables…
thanks for the elaboration. I was referring to Type{Dict}
which should have the single “instance” Dict
(and not Dict
, which as you say has many instances).
I will try a bit forward with using the apply
way, and will keep your warnings in mind.
Thanks a lot
1 Like
I see — sorry I misunderstood. Thanks for clarifying.
Generally, I prefer to think of Type
as a special construct to use in method signatures, not as part of the type system (though technically it is). IMO using it for anything unrelated dispatch may be a code smell. The manual could use some advice/clarification about this.
1 Like
Belatedly, I just realized there are two conceptually related but not overlapping definitions for “singleton type” in Julia. The manual talks about Type{T}
as “the” singleton type, while it is a singleton type. Maybe that is what caused confusion here. I commented in
1 Like
I experimented a bit more with the apply
approach and indeed it turns out to be more difficult than expected.
Concretely, nested apply
constructs do not seem to work. I opened an issue for this and also found a workaround which you can find there: https://github.com/JuliaLang/julia/issues/36626.
To repeat the workaround in this context, it solves some inference problems if you use
good_inference_apply(f, args) = f(args...)
instead of bad_inference_apply(f, args...) = f(args...)