-
-
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
add function f()::T
return type declaration syntax
#16432
Conversation
Does this allow declaring a return type on a generic function definition (i.e. |
The conclusion we came to at some point in the #1090 discussion was that because the effect of that kind of type declaration is non-local, it should not implicitly call convert, but rather implicitly add type assertions so that adding a type annotation would cause an error rather than silently changing behavior. |
What scope does it get evaluated in? It seems like it could use a few more tests to make sure odd cases get lowered correctly (e.g.
but that is not what it implemented here? |
It is not – on this branch: julia> function foo::Int end
ERROR: syntax: expected "(" in function definition
in eval(::Module, ::Any) at ./boot.jl:226 |
right, but I mean it is also not what the visually similar |
No, that is not implemented here.
I evaluate it inside the function, at the very top.
and |
What manual chapter should describe this? My first thought was |
I like the idea of putting it in types, along with descriptions of typeassert and dispatch syntax. |
Right after dispatch seems like the right place to put it. |
Thanks. So the way I'm thinking about this is as a method-specific feature: for a specific method definition, I can declare a defined return type. This has benefits for self-documentation, code readability, and general "type cleanliness" in that we're essentially adding explicit type declarations for return types in a method. Can I request one more change to go along with this PR in the spirit of the first 2 benefits (self-doc and readability)? Can we add the declared return type, if any, to the output of, for example, We probably also want to plan on doing a mass update of some kind to put return types on things in Base and possibly update our documentation style to use this syntax; i.e. I think we often use the |
I don't want to use the information in these declarations too extensively. If people expect to see it e.g. from This is implemented in the front-end by rewriting all However we could have showing |
Fair enough; as I thought more about that, I realized it would probably require more machinery than we probably want to commit to at this point. |
Sorry if this is a dumb question, but does this support doing operations on types, something like
? |
Is something like (Should be tested.) |
Yes; the type expression is evaluated on entry to the function.
Run-time; equivalent to running |
How is "on entry" defined, i.e what does: function op(a)::(rand(1:2) == 1 ? Int : Float64)
return 1.0
end do? Pick one when the function is compiled a new one every time the function is called? |
It is defined here #16432 (comment) Only one version will be compiled. |
If you write function f(args...)::<expr>
# stuff
return <r1>
# futzing
return <r2>
# junk
return <r3>
end it is transformed into something like this: function f(args...)
R = <expr>
# stuff
return convert(R, <r1>)::R
# futzing
return convert(R, <r2>)::R
# junk
return convert(R, <r3>)::R
end The return type expression |
Thanks, I got it now :) |
@JeffBezanson should the return type declaration be checked to ensure that it is a type? julia> ex1{T}(x::T) :: (T,T) = x-1.0, x+1.0
ex1 (generic function with 1 method)
julia> ex1(2)
ERROR: MethodError: First argument to `convert` must be a Type, got (Int64,Int64)
in ex1(::Int64) at ./null:0
in eval(::Module, ::Any) at ./boot.jl:225
in macro expansion at ./REPL.jl:92 [inlined]
in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:46
julia> ex2{T}(x::T) :: 2 = x-1.0
ex2 (generic function with 1 method)
julia> ex2(2)
ERROR: MethodError: First argument to `convert` must be a Type, got 2
in ex2(::Int64) at ./null:0
in eval(::Module, ::Any) at ./boot.jl:225
in macro expansion at ./REPL.jl:92 [inlined]
in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:46 |
This can only possibly be checked at runtime and it is effectly checked there already. |
Hmm, I have to ask then: why does this throw an exception upon declaration? julia> ex3{T}(x::(T,T)) = x
ERROR: TypeError: Tuple: in parameter, expected Type{T}, got Tuple{TypeVar,TypeVar}
in eval(::Module, ::Any) at ./boot.jl:225
in macro expansion at ./REPL.jl:92 [inlined]
in (::Base.REPL.##1#2{Base.REPL.REPLBackend})() at ./event.jl:46
julia> ex4{T}(x::Tuple{T,T}) = x
ex4 (generic function with 1 method) |
The argument types have to be evaluate at definition time (otherwise you'll have to execute arbitrary code during dispatch). It is certainly possible to add that constraint to return type declaration too but it would be less useful (or at least you can't call |
Because the signature "runs" as part of the method definition. You can't add a method at all unless you have a type to sort it under. We could evaluate the return type at definition time too, requiring it to be a well-formed type. But (1) this would prevent uses like |
Thanks @yuyichao and @JeffBezanson. Return types are more powerful than I realized. |
Would you expect this to have an effect on memory consumption? AppVeyor has failed with memory issues in LLVM twice in a row now: Maybe bad luck? edit: 3rd time was the charm apparently... |
Merge? |
This seems to be using |
Seems worth looking into. I looked around, but so far I haven't found any examples that this lowers differently. However I did find a small problem; PR updated (I realized I should use |
Being greedy: I wonder if Void should be special cased https://groups.google.com/forum/#!topic/julia-users/4RVR8qQDrUg
|
Oh man, I thought this had been merged already! I coded up an entire new package using the syntax. Excited for this! |
So I'm seeing an interesting interaction with docs with this change, julia> "test doc Int"
test1(x::Int) = 2x
test1
julia> "test doc Float64"
test1(x::Float64) = 4x
test1
julia> "test doc Int"
test2(x::Int)::Int = 2x
test2
julia> "test doc Float64"
test2(x::Float64)::Float64 = 4x
WARNING: replacing docs for 'test2 :: Union{}'.
test2 note that for |
Probably just needs some minor updates to |
E.g. "..." f{T}(x::T)::T = x Fixes problem mentioned in JuliaLang#16432 (comment)
Was a simple fix as far as I can tell: jb/rettype...MichaelHatherly:mh/fix-doc-rettype Can either be cherry picked to this branch or I can open a separate PR, I don't mind which. |
E.g. "..." f{T}(x::T)::T = x Fixes problem mentioned in #16432 (comment)
This's awesome! :) |
In the gitter we were discussing how to make it more convenient to return pushone!(xs)::Void = push!(xs, 1) # currently an error You can actually do this already by defining One solution might be to have a Just a suggestion anyway. |
Would it be terrible if we just had a definition of convert(::Type{Void}, x) = nothing ? Seems like that would allow what @MikeInnes said without needing an extra |
Oh, I read @MikeInnes comment in email and it looks like he edited to include this exact suggestion. Sorry for the noise. |
As discussed on the mailing list, this doesn't actually seem to be using Can we add the typeassert please? In addition to checking for buggy |
Not sure how that happened. Will fix. |
x-ref: #18899 |
This implements #1090. Uses the same approach as other variables with declared types, i.e. return values are wrapped in
convert(T, val)::T
. I think we've waited long enough for this feature! :)