Argument-specific docstrings or comments

I think it would be great if there was some official syntax for specifying a docstring directly for function arguments. “Official”, as in, the information should be readable by third-party packages like Juno, to build tooling on top of.

As it stands, you get at most two pieces of information when typing in a function name and seeing the list of arguments (in Juno). That is the argument name and its type. But often, the arguments are named in a way that does not explain what they are about. Sometimes you might infer the meaning from the type restriction, but often there is none. Let’s say you want to execute a function model() and you type it in, when you see this:

Now you don’t really know much unless you are a domain expert and know already what the greek letter stands for or what qef means (I made these up). You also don’t know what will happen to the kwargs when you supply any.

In Swift, for example, there are argument labels, which help understanding what the arguments of the function are for. I’ve always found those labels quite useful. Here’s an example from their docs:

Even though it’s already useful, a one-word label is very limited (although it has to be used when calling the function, so it is a more strict requirement than I would like). Maybe something like this would be better:

function model(
        μ::Real, # The amount of damping applied to the model
        qef::Rational; # The quantum entanglement factor
        kwargs... # kwargs will be forwarded to `compute_model()`
    )

Or even as real docstrings, so they exist as julia objects, are therefore more easily accessible for third party packages, and interpolation works:

function model(
        μ::Real, "The amount of damping applied to the model"
        qef::Rational; "The quantum entanglement factor"
        kwargs... "Kwargs will be forwarded to `compute_model()`"
    )

A counterargument I can imagine would be “This is what the function docstring is for”, but I believe there is a case for a short description that belongs directly to the argument. The function docstring could then expand on the meaning of the variable even more, but the time needed to understand the arguments would be much less in my opinion, if a short explanation was directly available. The full function docstring is quite a long text if you have many arguments, and people will probably not want to read it every time they want to know something about a parameter.

Imagine hovering the cursor over a function argument in some code you don’t understand yet, and a tooltip pops up that just displays this argument docstring.

This syntax could even simplify writing the function docstring itself, because it could be appended in a nicely formatted way to the main function. At least I am usually too lazy to copy argument names to the docstring and nicely format a list of descriptions (also because I often forget updating the docstring when I change the argument name later).

21 Likes

I like this idea. Could you file an issue proposing this feature? This is a syntax error:

function model(
     μ::Real, "The amount of damping applied to the model"
     qef::Rational; "The quantum entanglement factor"
     kwargs... "Kwargs will be forwarded to `compute_model()`"
)

which is good news since that means the syntax is available. It would also need a way to annotate documentation metadata to individual arguments and to query it.

11 Likes

It seems more logical but perhaps less readable to place the separator after the docstring, like this.

function model(
     μ::Real "The amount of damping applied to the model",
     qef::Rational "The quantum entanglement factor";
     kwargs "Kwargs will be forwarded to `compute_model()`"...
)

I’m not sure which is better; just putting it out for consideration

10 Likes

I agree. Then I’d kinda want to move the string before the argument to match how we generally document things… but that becomes even more unreadable yet for function arguments. It works fairly well in the context of struct fields, though, and that’s been proposed before IIRC.

function model(
     "The amount of damping applied to the model"    μ::Real,
     "The quantum entanglement factor"               qef::Rational;
     "Kwargs will be forwarded to `compute_model()`" kwargs...
)
5 Likes

Even though t’s more consistent with how we write function docstrings, I find this less nice. It seems to make more sense to me to put the description (if any) after the argument name.

1 Like

To me this seems better (putting the docstring before the comma), because putting the docstring after the comma means the 1-line version is quite odd:

function model(μ::Real, "μdocs" qef::Rational; "qefdocs")

Here, it looks like “μdocs” is much more associated to qef than μ.

It seems like this usually wouldn’t be used in a one-line format since it’s quite cramped, but it seems better if the syntax at least makes sense either way.

4 Likes

I think I like the after argument and before separator version the most. I’d argue that in terms of readability or ease of understanding, a string that comes after the name and is enclosed by it and a separator can’t possibly be confused for belonging to a different argument. The reason for the function or struct docstring placement is that it’s also more easy to read that way, and doesn’t get in the way within the function itself. I think even multiline strings with this style aren’t half bad:

function some_function(
    a::Int "How many a's should be computed",
    b::Real """This is another parameter description that I've purposefully made
        even longer than the one before""",
    c::Rational """I think it would not be very difficult to visually parse this
        argument list, as the docstrings don't distract all that much from the
        arguments sticking out on the left.""",
    d::UnitRange """Now that I'm writing more multi-line comments I start to really
        like this, because it's always nice if the information you're looking for
        is close to where you start looking for it. In this case, right here!"""
    )
6 Likes

I’d like to do that, could you maybe point me to the correct place to file the issue? Simply the main Julia repo?

1 Like

If this form allows another linebreak I could definitely live with it.

function model(
    "The amount of damping applied to the model"
    μ::Real,
    "The quantum entanglement factor"
    qef::Rational;
    "Kwargs will be forwarded to `compute_model()`"
    kwargs...
)
10 Likes

Yes that’s also good, I think like with normal docstrings there should be no difference between a line break or other whitespace. Writing the docstring into the line above the argument could actually be a nice convention.

1 Like

While I can see how this could be useful in some contexts, and have a slight preference for the docstrings before arguments (for consistency), I think that having 3+ opaque arguments to a function may be considered code smell (with passing around keyword args as a special case), especially in Julia where we have very nice zero-cost abstractions.

Eg instead of the original example, I would consider something like

struct Model{T1,T2}
    "The amount of damping applied to the model"
    damping::T1
    "The quantum entanglement factor"
    quantum_entanglement_factor::T2
end

struct ComputationParameters
    ... # similar, with a default constructor, eg with Base.@kwdef
end

function compute_model(model::Model, computation_parameters = ComputationParameters())
    ...
end
1 Like

I think that there are definitely cases where you have quite Julian functions with 3+ arguments that are a little opaque to non-experts.

I am a little confused about how all of the infromation gets diplayed. If you let the argument-specific docstrings become too long, then you will likely get inundated if something like Juno displays the docstrings as you are typing. In that case, I don’t see how displaying argument-specific information is better than just displaying the whole docstring.

That said, I can still see value in having all of the argument-specific information displayed to you along with the docstring when you run
?model.

What about taking inspiration from e.g. JavaDoc and put the documentation for each parameter in the docstring of the function? It would avoid unnecessary clutter in the function arguments themselves.

for example:

"""
<docstring>

@param a The string given to this function. 
@param b The int given to this function. 
"""
function foo(a::String, b::Int)
# the ... 
end

Java and its problems aside, I’ve always liked this style of documenting the parameters.

2 Likes

That’s already available/recommended as a best practice in markdown form:

# Arguments
- `n::Integer`: the number of elements to compute.
- `dim::Integer=1`: the dimensions along which to perform the computation.

The IDE could parse that.

13 Likes

Note, this approach is used in the Modelica modelling language since year 2000. In Modelica, it is more general: A description string can be added after every function definition, after every function argument and before every “;” of a statement/equation . There are then report generators that generate a pretty-print version, e.g. a list of all functions with just the function name and the description string; or a function with the input/output arguments in a table with description strings. Example:

image

Furthermore, tools provide the possibilty to pop-up an automatically generate GUI from the given information so that the arguments of the function can be given in this GUI and by clicking a button, the function is executed with the arguments provided in the GUI. Example:

image

4 Likes

That does seem like it might work out better than trying to annotate individual arguments, which, as we’ve seen already just in trying to scope this out here, gets a bit messy.

6 Likes

I think the ... should still go after the argument name, it isn’t really a separator, such as , or ;, after all.

1 Like

I personally don’t mind the additional strings in the argument list of the source file so much (syntax highlighting and indenting makes it pretty easy to distinguish, although monochromatic code would be a different story). Mostly you read what the auto-complete shows you when typing a function name, and there you wouldn’t immediately show all argument descriptions by default. In Juno, e.g., I would just show the description for the argument that I’m currently typing, to help me determine if I’m supplying the correct value.

I think conventions for formatting function docstrings a certain way so that other tools can read parameter descriptions out of that are a brittle way of documenting arguments. Especially, as I argued above, because that takes much more time to do correctly (you probably need to look up the correct convention multiple times, because it’s not standard syntax), and because the name and position doesn’t update when you change an argument in the function. With a string above the argument, it moves around with its argument when reordering them, or deleting them, and the argument name is there only once. With a function docstring, you have to keep these things synced manually.

I think that’s much higher maintenance if you accumulate over many many functions. And most people (including me) already skimp on documentation, maybe this would help lower the threshold to do it. The single argument is the smallest unit of a function that has a meaning, and if you quickly explain a couple of arguments, together with the function name you probably don’t even need much of a docstring anymore.

2 Likes

I disagree - that style of documenting is widely used and isn’t limited to Java, the basic core of JavaDoc is somewhat compatible with the doxygen style of C land. Both of those languages still continue to thrive in part because the most important building blocks are extremely well documented.

This is not a requirement though - since the docstring has to be parsed either way to find out which docstring belongs to which argument, the order as it’s written in the docstring of the function does not matter. It’s also mostly irrelevant for reading, since in your example the displaying of the documentation would be done by the IDE anyways and thus correctly matched.

Personally, I favor maintainability and readability of the function on its own over shuffling around potentially long strings in the argument list itself. Let’s also not forget optional arguments here - having the docstring of that argument inline would make it very hard to parse at a glance which is the docstring and which is the default value.

In summary, I doubt having inline documentation of arguments is going to incentivice people to write more documentation. From my experience, if someone doesn’t want to write a short text explaining what their new thing does, they’re usually content with a (in their mind) descriptive variable name without any thought whether that name is overloaded with too many different meanings or whether newcomers to their code can easily jump in and use it because everything is explained clearly.

1 Like

Just to mention: The julia-intellij plugin has this already implemented:
See here