Skip to content

Conversation

LilithHafner
Copy link
Member

In cases where a function is documented as

```
    function(arg::ArgT, arg2::Arg2T) -> RetT

...
```

I either switched -> to :: or switched RetT to ret_name::RetT.

From the recommendation and justification from @nsajko here: #56978 (comment) and applied throughout the repo.

@LilithHafner LilithHafner added the docs This change adds or pertains to documentation label Jan 10, 2025
Copy link
Contributor

@mcabbott mcabbott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some point I made a branch switching to --> instead (but never made a PR). One of the goals was things like this, where what is returned seems more important than the type:

lmul!(a::Number, B::AbstractArray) --> B     # mutates & returns 2nd arg

eigvals!(A, B; sortby) --> values::Vector    # returns neither arg

I see the PR would make these eigvals!(A, B; sortby) -> values::Vector. Perhaps that seems awkwardly almost-code (same complaint as the present ->)? My idea was to make a smaller visual change, and move in the direction of clearly-not-syntax.

I also had these... which do parse as capturing :(s --> Bool) but at least --> has no definition, while s::Bool does mean something which isn't true:

@isdefined s --> Bool

 @__LINE__ --> Int


"""
resize!(a::Vector, n::Integer) -> Vector
resize!(a::Vector, n::Integer) -> a
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or switched ... to ret_name::RetT.

Took a while to find an example of what exactly this meant.

If the reason to use :: is that this is valid syntax, does this want to use -> when it's not?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm inclined to use -> a because while it is not valid syntax, if it were, the only plausible literal interpretation would be correct up to omission of the implementation. ::Vector would also be fine, though less helpful. -> Vector, if interpreted literally, is wrong, hence the change.

I'm not as concerned with weather a docstring uses literal syntax as with weather, when interpreted as syntax, its semantics are correct.

@jmert
Copy link
Contributor

jmert commented Jan 10, 2025

At some point I made a branch switching to --> instead (but never made a PR). One of the goals was things like this, where what is returned seems more important than the type:

lmul!(a::Number, B::AbstractArray) --> B     # mutates & returns 2nd arg

eigvals!(A, B; sortby) --> values::Vector    # returns neither arg

What about using assignment syntax to name variables instead?

B = lmul!(a::Number, B::AbstractArray)

values = eigvals!(A, B; sortby)::Vector

maxval, index = findmax(A; dims)

@jishnub
Copy link
Member

jishnub commented Jan 11, 2025

Previous discussion on this at #22804

@LilithHafner
Copy link
Member Author

Reading the previous discussion and existing docstrings, I propose that :: is used to indicate return types and -> is used to indicate return values, following these patterns:

    f(x::T)::U
    f(x::T) -> y::U
    f(x::T) -> (y::U1, z::U2)
    f(x::T)::Tuple{U1, U2} # valid and reasonable, but I'd prefer the option above 
    @mac x -> y::U
    @mac -> y::U
    @mac::U # Not this, it's not valid syntax
    @mac -> U # Also not this, it's inconsistent with the forms above. Provide a variable name for the output.
    U(x::T) # Don't annotate the return type if it's obvious

@laborg
Copy link
Contributor

laborg commented Jan 11, 2025

I like the idea from @mbauman to use → instead of -> to distinguish it from anonymous functions. If this is used, I'd also prefer having it consistently, even if only the type is specified:

f(x::T) ⇥ # when nothing is returned
f(x::T) → ::U
f(x::T) → y::U
f(x::T) → (y::U1, z::U2)
f(x::T) → ::Tuple{U1, U2}

@nathanrboyer
Copy link
Contributor

nathanrboyer commented Jan 12, 2025

I like the unicode arrow idea aside from the first case. The line at the end in is too hard to see. I'd just write f(x::T) → nothing.
If the original proposal is kept instead, I'd prefer the :: case to have spacing like the -> case so the return information is distinct from the function signature likef(x::T) :: U.

@dkarrasch
Copy link
Member

Small nit: This is a bad idea

B = lmul!(a::Number, B::AbstractArray)

as one can see from the next example

values = eigvals!(A, B; sortby)::Vector

which could be rewritten as

A = eigvals!(A, B; sortby)::Vector

That is, in cases where memory is mutated, but the returned object is something different, one could use the same symbol and just redirect the variable binding. We should make clear that it's the very same B in lmul!(a::Number, B::AbstractArray) that is returned, and that you don't need to make a new assignment for the result. I believe this is how people use it generally anyway.

@MasonProtter
Copy link
Contributor

Nice job taking this on @LilithHafner!

I must say though that I really think -> shouldn't be used at all for returned values either. It seems like a pointless instance of pseudosyntax that conflicts with real syntax.

@LilithHafner LilithHafner added the triage This should be discussed on a triage call label Feb 14, 2025
@LilithHafner
Copy link
Member Author

From triage: write down a guide on what style is appropriate here (basically what this PR does) in a separate PR in the style guide. Then implement that style here.

@nickrobinson251
Copy link
Contributor

nickrobinson251 commented Feb 28, 2025

Sorry to be late to the party here.

I wanted to put in a vote against using :: for return type annotations in docstrings

because although this matches well the meaning of callsite annotation (x = foo(a, b)::Integer) it is generally not a good idea to annotate definitions with return types in this way (foo(a, b)::Integer = 42), because the definition annotation becomes a convert call and that can have a run time cost https://github.com/RelationalAI/RAIStyle#return-type-annotations-in-function-signatures-can-cause-runtime-cost

And my fear is that by making the syntax foo(a::Int, b::Bar)::Integer present in many places directly above function definitions, it might become more common for folks to write function definitions that same way, e.g.

"""
   foo(x::Int, y::AbstractBar)::Integer
   
Return a lovely integer
"""
function foo(x::Int, y::AbstractBar)::Integer
  ...
end

which could have unintended perf consequences.

I say this as someone who has had to go through a large codebase and remove many of these unintended type conversions that sometimes had perf costs (the original authors thought this syntax was a type assert).

@nickrobinson251
Copy link
Contributor

nickrobinson251 commented Feb 28, 2025

genuinely interested in your 👎 emoji @MasonProtter -- can you say more about it? Do you think the worry about the unintended consequence (i.e. declarations annotated with conversions becoming more prevalent) just isn't realistic?

@MasonProtter
Copy link
Contributor

MasonProtter commented Mar 1, 2025

I'm sure it's possible for it to have unintended consequences, but I don't really believe that the cross-section for negative outcomes is worth having a confusing / inconsistent documentation style.

I don't really see why someone reading a docstring that says

"""
   foo(x::Int, y::AbstractBar)::Integer
   
Return a lovely integer
"""

would cause them to write

function my_function(x::T)::U
    [...]
end

as their implementation, and even if they did, it would take some pretty specific circumstances for that to actually have a noticable performance impact (i.e. the Integer case isn't a good example because that conversion is free unless you write a specific convert method for a custom type that makes that not free)

Ultimately, I just think it's useful for us to document expected return types, and I think that documenting them as

"""
   foo(x::Int, y::AbstractBar) -> Integer
   
Return a lovely integer
"""

is worse.

@jonas-schulze
Copy link
Contributor

If one is concerned that users would copy parts of the documentation, I'd say it is just as likely they may copy parts to the call site (as opposed to the definition site). Consequently, the suggested documentation style may just as well help users find broken edge cases:

help?> foo
search: foo floor fourthroot pointer_from_objref OverflowError RoundFromZero

  foo(x)::Int

  Return a lovely integer.

julia> foo(2)::Int
4

julia> foo(2.0)::Int
ERROR: TypeError: in typeassert, expected Int64, got a value of type Float64
Stacktrace:
 [1] top-level scope
   @ REPL[15]:1

@LilithHafner LilithHafner added the triage This should be discussed on a triage call label Mar 13, 2025
@JeffBezanson
Copy link
Member

The problem is that Type -> Type is Haskell syntax that we don't use anywhere in the language except some docstrings. It seems perverse to use invalid / from-another-language syntax in docstrings to prevent people from imitating them. Docstrings are also primarily for function callers, where it should be pretty clear the :: is not required.

The main alternative I can think of is f(x) isa RetT, which reads correctly as julia but is not valid as a definition. But it has the downsides of (1) being uglier and (2) not working as a call (since it gives you a Bool instead of the value).

@StefanKarpinski
Copy link
Member

@JeffBezanson, I'm not sure if you're arguing against using -> T at all in docs, or just against using it in the case when the doc could be written with the :: T syntax that is actually valid Julia.

When I use -> what as a doc string syntax, it's intentionally invalid because it's expressing the behavior of a function in a way that is more for the human than the language and isn't expressible with ::. The docs for Tar.jl have many examples of this, like:

create([ predicate, ] dir, [ tarball ]; [ skeleton ]) -> tarball

This indicates that the thing that's returned is the tarball path, which may be passed in or a tempname that's generated for you. If the information that's conveyed can be expressed in actual Julia syntax, it should be expressed using actual syntax. If the information is something else, then I think it's fine to ues non-syntax, especially if we standardize a convention for it.

I do think there's an undercurrent here that some people thing that the function return type with implicit conversion is a misfeature and don't want to encourage people to use it. While I think it may have been better for return type annotation to do assertion, I don't think what we have now is so bad. There's nothing wrong with using this feature in the language and there's nothing wrong with using it in the documentation or encouraging people to use it.

@LilithHafner
Copy link
Member Author

FWIW I was talking with Jeff when he wrote that comment and AFAICT it is an argument against -> Type not against -> return_value.

@LilithHafner LilithHafner removed the triage This should be discussed on a triage call label Mar 23, 2025
@LilithHafner
Copy link
Member Author

IMO this is ready to merge except for @mcabbott's #57012 (comment)

@LilithHafner LilithHafner added the merge me PR is reviewed. Merge when all tests are passing label Mar 25, 2025
@LilithHafner LilithHafner merged commit 58deac9 into master Mar 26, 2025
8 checks passed
@LilithHafner LilithHafner deleted the lh/arrow-to-colon-doc branch March 26, 2025 12:05
@LilithHafner LilithHafner removed the merge me PR is reviewed. Merge when all tests are passing label Mar 26, 2025
serenity4 pushed a commit to serenity4/julia that referenced this pull request May 1, 2025
In cases where a function is documented as 
```
    function(arg::ArgT, arg2::Arg2T) -> RetT

...
```
I either switched ` -> ` to `::` or switched `RetT` to `ret_name::RetT`.

From the recommendation and justification from @nsajko here:
JuliaLang#56978 (comment) and
applied throughout the repo.

As documented here JuliaLang#57583

Also includes some minor changes to touched lines (e.g. removing annotations that are just clutter)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs This change adds or pertains to documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.