Julia: Request for feature: verifying that a function call return type is inferrable to a concrete type

Created on 14 Apr 2018  Â·  19Comments  Â·  Source: JuliaLang/julia

I would like to request a language feature that asks the compiler to detect whether the result of a function call can be inferred to a concrete type, and if not, to throw an error. This feature should be usable in production code, and hence should have no impact on run-time performance. The syntax could be in the form of a macro call or variant of a type assertion. The purpose is to make the functionality provided by code_warntype more useful. Here are some examples of how it would work. Let us say, for the purpose of presenting examples, that it is implemented as a type assertion wherein the asserted type is true, which AFAIK is meaningless in the current language.

    u = a > 0 ?  1 : 1.0
    # The following throws an error; the compiler cannot infer a concrete type for the result of +
    b = u + 1 :: true   
    x = Float64[]
    push!(x, 6)
    # The following does not throw an error; the compiler infers the type
    # of the result of Complex{typeof(eltype(x))}
    y = Complex{typeof(eltype(x))}(2.0) :: true  

(This is my attempt to reformulate https://github.com/JuliaLang/julia/issues/26809 in a more acceptable manner.)

All 19 comments

Can you explain what you mean by “a concrete type” in this example? Do you mean “doesn’t allocate”? The above code doesn’t allocate or have any dynamic behavior. Do you mean “inferred to remove runtime cost”? Only programs that can be folded to a constant have that property.

typeof(eltype) is a Type (specifically, DataType here). That expression would always throw an exception

Yes, I made a mistake in my example, sorry, it should just say Complex{eltype(x)}. I think concrete type already has a well-defined meaning in the language: complement of abstract type, right? Concrete types are the types that can be returned by the typeof function (i.e., types that objects can actually have).

As I said, this can only be implemented as a feature of something like code_warntype.
In this case, the main reason we won't implement anything like this (i.e. throw an error based on inference result) is that this makes runtime behavior inference dependent again.

Again, the information (inferred type, line numbers) you want are all in code_warntype (or code_typed) and you just need to extract what you want from there. This is best done in a package and can be combined with a source code marking of your choice.

I don't understand this answer; is the functionality I am requesting already available? If so, can you provide a hint how to find? Or are you saying there is a simple way for me to implement it myself? Or are you saying that it is actually not useful?

I'm saying that it's not useful/not acceptable as a runtime check (but could be useful as a debug feature).
The information you are requestion is in code_typed (since it shows all the inferred type of your function).
I don't know what form of this information will satisfy you but combining the line number and the type info should be enough to implement something you need.
I'm not saying it can be easily implemented but I am saying it can be and should be done in a package as a debug feature instead of introducing another runtime function with strong inference dependent behavior.

I'm still not understanding what you are proposing. Using code_typed has at least two disadvantages: (1) I have to know the input types of the function to be tested, which might be difficult if the function is buried deep in the code and is possibly called several times with different signatures (i.e., it is generic), and (2) the output of code_typed is often difficult to understand. Can you explain in more specific terms what you mean by "package"? What would it do and how would I use it?

Can you explain in more specific terms what you mean by "package"?

Errrr, I mean package ? Is there any ambiguity?

has at least two disadvantages
What would it do and how would I use it?

It should do what you want and you should use it how you want? I'm not proposing any exact implementation so these questions are impossible to answer. What I'm saying is simply that while the output of code_typed is low level and could be hard to use in some cases, it has all what you need and you can write code to look for those information if you don't want to do so manually. Such a need is certainly valid but it just shouldn't be a runtime feature. It should also start in a package, which can certainly be a stdlib one after proving itself, and not in Base.

Also, as Jameson points out, there are also other issues with implementing this as a runtime feature rather than a debugging one. The inferred type should not affect the runtime behavior so this feature will not be useful to check the behavior of the program. It can only be useful to check the performance. However, compiler optimizations are always somewhat non-local and almost any particular local measure of it will fail to reflect the actual performance figure at one point or another as the optimizer improves (as an example, union types already are already affected by the improvement in optimizer in 0.7) making the usefulness of such feature limited in the long term.

Concrete types are the types that can be returned by the typeof function (i.e., types that objects can actually have).

Right, but that would make the assertion even more empty – the compiler/runtime knows with absolute certainty that all objects have a concrete type, since it's a language guarantee that all objects must be well-formed.

OK, I see your point about the new implementation of small unions. So I agree that in the long run, there may be cases when this feature is not useful. And I also agree with your conclusion that it is fine to put this in a package instead of Base. But I'm still not clear on how a package could possibly work for this purpose. Suppose my package is called checkinferred.jl and it defines one macro called @checkinferred. Suppose the macro looks like this:

@checkinferred  function myfunc(a)
      u = a + 1 :: CHECKINFERREDRIGHTHERE
end

Now the macro sees a statement for which I ask for inference to be checked. What does it do? Presumably, it has to modify the code in some way so that there is a hook to call code_warntype each time the compiler is called for myfunc and somehow parse the output of code_warntype right after compilation? How can user-level code even tell that the compiler is being invoked? Is there any existing package with an analogous feature so that I could understand better what you have in mind?

In response to vtjnash's comment, the proposed type assertion is not asking whether the object has a concrete type. Rather, it is asking whether the compiler is able to infer the object's concrete type. So I don't think that the assertion will always be true.

What does it do?

It can figure out these from the line number, which I mentioned repeatedly above. Note that for the cases that optimization makes this unreliable (e.g. if the entire line is gone) it'll also be the case where doing the check is pointless...

@yuyichao, after further thought, I have an idea for how such a macro could work. It could replace myfunc with a generated myfunc. It is already a language feature that the compiler is called for the generated function each time a new type signature is encountered, and now the macro can intervene in the compilation. The macro could insert calls to code_warntype in the generated function on the expressions or statements for which the user wishes to check inference. Do you think this could work? It sounds complicated.

You don't need generated function. Just a wrapper is enough if you just want to capture the call.

@yuyichao, please be more respectful of issues that someone has taken the time and effort to open and don’t just close it because you don’t like the request or think it is nonsensical. The least we can do is have a civil and polite discussion of the request and, if it's impossible, explain clearly why it cannot be done and try to explore how the goals that motivated the request in the first place might otherwise be satisfied.

Yuyichao, I don't understand "capture the call". Does this mean, "put in some statements that will be invoked either before or after the invocation of the call itself"? This is what @inferred does already, and it is not what I am seeking. I want the macro to do its magic at compile time, not run time. The goal is to insert this macro into production code, so it shouldn't have any impact on run time. Again, if you could indicate a package that has some kind of related functionality, I could try to figure out how it works. Otherwise, I will continue to think about using generated functions for this purpose.

I want the macro to do its magic at compile time, not run time. The goal is to insert this macro into production code, so it shouldn't have any impact on run time.

That (making this a runtime feature, instead of one that can be enabled for debugging/testing) is exactly what I've proved to be impossible/unacceptable above. Doing this strictly at compile time is also impossible toi do since it's impossible for the compiler to inference anything without input type. If you want to give some input type, there is indeeded some discussion (have to search, don't remember exactly where it was) about making it easier to do code_typed recursively. That'll be a different issue on it's own and is another feature that can be implemented in a package.

explain clearly why it cannot be done and try to explore how the goals that motivated the request in the first place might otherwise be satisfied

In exactly the comment I closed this issue above, I acknowledged something about code_typed could be done in a package and showed that it should be done in a package. Please read my comment fully and if the logic isn't clear enough, please ask, politely, which is what @StephenVavasis is already doing. I do think discussion what's possible to do with improving debugging experience is useful and I don't really mind continuing it here as it is. But it's just not something that should be made a (stable) runtime feature or something that belongs to base. That's enough to close the issue which, again, doesn't mean the discussion about a potential package has to end here.

I don't understand "capture the call". Does this mean, "put in some statements that will be invoked either before or after the invocation of the call itself"? This is what @inferred does already, and it is not what I am seeking.

And more direct reply to this. Yes that's what I mean since it's the only way you can get all the type information needed. It's not what @inferred do though since @inferred is applied on the callsite (i.e. a + 1) and @code_warntype (or @checkinferred) is applied on the calling function (i.e. myfunc), this is the difference I pointed out in https://github.com/JuliaLang/julia/issues/26809 (you want to know about the a + 1 but there isn't enough context to figure out that locally).

Again, if you could indicate a package that has some kind of related functionality, I could try to figure out how it works. Otherwise, I will continue to think about using generated functions for this purpose.

At compile time, generated function will give you strictly less information with more restrictions than the result you can get from recursive code_typed. It's possible that a hack on generated function can be made working more easily on simple functions so it won't be a terrible place to start. Given what generated functions are allowed to do though, I don't think it can work in more general cases.

Yuyichao, thank you for your detailed explanations. I think I have some idea of how to proceed on this. And Stefan, thank you for your ongoing efforts to keep the Julia community friendly. I see that Yuyichao has closed this issue, which is OK with me.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dpsanders picture dpsanders  Â·  3Comments

musm picture musm  Â·  3Comments

tkoolen picture tkoolen  Â·  3Comments

manor picture manor  Â·  3Comments

Keno picture Keno  Â·  3Comments