-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
return type declarations #1090
Comments
Glad to hear you're planning to do this. Will we be able to specify that another function only accepts as inputs functions with specific types of returned values? |
What you're talking about are function types, and I suspect we'll probably not support that, because covariance/contravariance/subtyping issues get pretty complicated and confusing. What kind use cases did you have in mind for this? We did intend to have this at one point, and the syntax |
The use cases are things like optimization functions: you want to insist that the function that purports to return a Hessian at a minimum is returning a Matrix. It's not so valuable that I'd worry about it if building support causes headaches. |
There will still always be run-time checks for things like that, and potentially compiler modes that can do inference and warn if you're doing something that looks wrong. But I'm not too sold on adding function types back in. They're a lot of trouble for little gain in a dynamic system like Julia. Of course, in Haskell you simply need them. |
Understood. I have no feel for these things, so I'm sure you're right. |
Well, I'm not at all sure I'm right, so there's that ;-) |
I agree with like davekong says on #1078 Uint8 + Uint8 = Int64 Looks too strange and unexpected. I understand what Stefan says on that issue, and I think that something like type declaration of returns values can help. Because maybe its better promote into Int for calculation, but it's good gives to the user the expected type. If you have and Array of Uint8 occupying N bytes on memory, in only and scalar multiplication you get an object consuming 4 times more memory... Or in my case, I defined my bitstype of 8 bits and the following promotion rule [ I'm not sure of let be T, Int or Uint8 ]
For simply get uppercase letters, I need to explicitly convert the output. But, if I set the promotion rule to NucleicAcid, I going to get a worse performance how Stefan point out before...
I know I can simply definite a function taking a NucleotideSeq and returning on the same type, and user are not going to see the explicit conversion... But. In general is not intuitive. I'm not sure if return type declaration can be useful here.
|
In the case of arrays, I agree. See #1641 . |
Having spent more time working with optimization, I have to say that am now much more interested in one day adding the ability to do dispatch on functions typed by the combination of their input types and their return types. It would be nice to have the ability to write separate definitions for:
|
This is why in all my optimization routines, I pass the gradient into the objective function. Once it's an argument, you can do dispatch on it. |
One reason I'd like to be able to specify return types for functions is that it would improve looking at functions in the REPL. With return types I can much more easily tell at a glance what the functions in the list given by |
If anything this would add a lot to the self-documenting nature of a functions source code. I really miss the type of the return value from the source / documentation of basically every other popular dynamic system. It would also help in mentally planning the body of the function while writing it. Would discourage implementation of annoying behavior like PHP's tendency to return a NULL, or a false, or a normal value. Such behavior could totally be made explicit to the reader with the union types. Looking forward to this feature! |
I'm working on a medium-ish Julia project. Even if this wasn't involved in the type system (i.e. if it just automatically added an assert to the bottom of the function so I don't have to do this manually everywhere), this would prevent a lot of errors and help with self-documentation |
I think the big win here is self-documentation. The ability to look at documentation and/or code and immediately understand the type intent is invaluable. |
The main question here is monotonicity. It would be a nice property if declaring that |
Admittedly not the best solution, but why not just document the most specific return type that is returned for any input types? That's what I've done while starting to document some packages. |
In one sense, allowing the most abstract method definition to constrain all other specific methods to return objects of the same type is a feature. It makes everything more predictable since you don't have to worry about whether a specific type will give unexpected results. Then, the author of the generic function has to choose the best abstraction level to leave enough room for more specific implementations (or leave it unspecified if needed). |
The monotonicity constraint is indeed a feature. The main motivation was to use it to let |
I definitely agree that it's a good feature. It's one that has to be rather carefully designed, however. |
The main thing I missed in Python compared to Matlab was that there are no return "types". Well, what Matlab does is not really return types but still, it specifies which variables are returned which helps both with documentation and correctness. So +1 for this. What @JeffBezanson originally proposed is just syntactic sugar, so as far as I can tell, it's just a matter of implementing it (or not). Although I would propose to have it as a type-assert and not a type-convert:
would be equivalent to
(for a discussion of the subtle difference to Jeff's original proposal see https://groups.google.com/d/msg/julia-dev/pGvM_QVmjX4/V6OdzhwoIykJ ) The somewhat related topic which has been discussed here, is whether the types of functions and methods should contain their calling and return signature. I think this a sufficiently different topic that is should actually be a different issue. However, the present issue could be a stepping stone for that one. |
I definitely see the argument for using a typeassert. But a big part of the value of doing a
and know you have a |
Keep in mind that LLVM is generally smart enough to figure out that if it has some code that goes from say |
I see. Two counter arguments:
Either way it would be good sugar to have. (These two answers also clear up some of the questions I had in that referenced mailing list post, thanks) |
Will this shorthand also be available for the function shorthand syntax?
Which, by the way, makes me wonder if Julia might benefit from the syntax |
Yes, that will be how it works. Currently |
Ah, that is quite nice! |
Yes, that's really nice. On the input direction, this is similar to why I think it would be nice to do implicit conversion to the declared type of an argument, e.g.: f(x::Nullable{Int} = 0) = x
f() # => returns Nullable(0) Otherwise you have to write this as f(x::Nullable{Int} = Nullable(0)) = x which just seems obnoxiously redundant and unJulian. |
so I'd be able to write something like...
|
No, this is not about function types which contain their return type. The best you can do is:
i.e. you'd need to manually pass in an instance of B. |
I was kinda hoping if the function knew its return type, I'd be able to use it as the key in the dictionary. Either way, I think it is an improvement. If nothing else, it should help with the documentation. |
Actually, this works:
Note that type-inference is fine irrespective of whether you use the return-type annotation. |
That is pretty awesome! I was hoping I had a way to look at a function and see what it would return for a type. Anyway - it is no big deal, it would make some code a bit more efficient, and make a couple of things a bit nicer. But overall? I can work around it pretty easily. |
Not sure what this tries to achieve but FYI |
My understanding was that the purpose is to annotate the return type. I'm aware that this comes at the cost of two function evaluations. Is there a way which avoids the two evaluations? (The frowned upon)
But yes, probably better to leave the annotation off. |
Hi guys, I recently came across the Sparrow programming language a static compiled programming language with very simple syntax, but with hper-metaprogramming capabilities, that is being able to go from runtime->compile time. I think that this is revolutionary step forward in computing and his approach would solve the challenges Julia has been facing. Since the language is static, it already has return types but it does not suffer from the problems caused by the compiler not recognising types for example the challenges with data frame (http://www.johnmyleswhite.com/notebook/2015/11/28/why-julias-dataframes-are-still-slow/). Its something the D community has been seriously looking at (https://forum.dlang.org/thread/[email protected]). You can find the link to the creator's PhD thesis - one of the best texts I've read on programming period. I think this approach is something to be seriously considered. |
I might be missing something not having read all the details, but this sounds to me like a well-understood tradeoff: yes, with a static type system you can have arrow types, and instead of performance problems from a lack of type information, you get a compile-time error. The case of DataFrame |
I'm hesitant to comment on an old, closed thread, but I would note that several of us have converged on translating DataFrames into row-iterators that generate rows as well-typed tuples (which is possible now that we have |
I didn't realise that the DataFrame issue was solved. @JeffBezanson why would you get a compile-time error? |
@johnmyleswhite does this mean that in the future DataFrames will be able to efficiently process tables with columns of arbitrary type or will the types be bound to a specific set? |
Let's have that conversation elsewhere and in a few months from now. :) |
Fair enough |
That's generally what happens when a compiler for a statically-typed language can't figure out the type of something. Which will happen eventually. To say much more we'd have to drill down on what "approach" you're talking about more specifically. For example Sparrow supports calling functions on constants at compile time, but the name-to-type mapping in a DataFrame isn't a compile-time constant. +1 To row iterators. I've had success using tuples and NamedTuples with that approach. |
As far as I can see from his thesis he has developed semantics for the user to decorate what should be done in compile time and run time as well as the default state where the compiler works out what should be done depending on the nature and typing of the inputs. "If a metaprogram has bugs, it will cause the compiler to crash or behave incorrectly" as would be the case in a run-time system. But I think he is the best person to speak about this aspect |
I think however, it would certainly be possible to create rules that deal with this aspect as a sort of compile time exception handling |
I have a completely type stable row iterator for DataFrames in Query here. It uses NamedTuples to represent rows and works great. You can write arbitrary queries against DataFrames with Query and there are no type instability problems because that iterator essentially solves that problem |
@JeffBezanson one of the major points of statically-typed languages is to get errors. They show you that most probably you got something wrong. In my view, if you want efficiency, you would better be in this category of languages. Moreover, I would argue that it's really important for one to easily figure out how a particular code would be translated in the machine language (at least to some degree). If the language is allowed to do a lot of "clever" things that makes it harder for one person to actually understand the performance characteristics of the code. That inevitably leads to less efficient code. I think the example illustrated by @johnmyleswhite in http://www.johnmyleswhite.com/notebook/2015/11/28/why-julias-dataframes-are-still-slow/ is perfect here. If, on the other hand, the language allows you to write simple code that everyone understands how it translates to machine code (again, to some degree), then, the language must make it pretty clear what run-time is. That means the language needs to create a clear distinction between run-time and compile-time. Yes, the distinction between run-time and compile-time can be annoying in some cases, but I would argue that those cases appear very seldom in practice. After all, we know that our programs will never be like "compiler, please solve my problem (and you should be able to infer which problem I'm referring to)" My 2 cents, |
Let's not continue this broad design conversation on this issue as every comment sends a notification to many people. |
@lucteo: Julia is not currently a static language and won't become one in the future, so I'm not sure what the point of your comments is. The alleged simplicity of having semantically different compile-time and run-time phases is contradicted by the many confusions and troubles that arise from this distinction in static languages: virtual vs non-virtual methods, overloading vs dispatch, etc. – these are some of the most chronically problematic and hard-to-explain issues in static languages. |
I'll reply on the julia-dev list. |
x-ref to julia-dev thread: https://groups.google.com/d/msg/julia-dev/N9mj_eI9wCE/78AwQ71YAAAJ |
Another problem is if the argument list goes too long and you want to put the |
If you put the closing
It may be more aesthetic to do it this way:
|
Provide this convenient shorthand:
for this:
The text was updated successfully, but these errors were encountered: