This question is a bit philosophical, but itβs probably worth thinking about. Given that we strive for generic functions in Julia, it seems that functions should only have one docstring per arity, each of which describes the meaning of the function for a particular arity. In other words, foo(x, y) should only have one meaning, regardless of the particular types provided as the first and second arguments.
Here are a few Base functions that have more than one docstring per arity:
Of course not (at least not always). If dispatch is good for Julia code, why wouldnβt it be good for Julia documentation?
Suppose thereβs a function f, which may be called with a number of arguments, where the first argument is dispatched on (it could be some kind of option argument). IMO, it can be nice to decompose the doc entries so that different types of the first argument get separate doc strings. But it depends on the situation.
Meta: I think this topic would fit into General usage better than into Internals & Design.
Ideally yes. Changing the contract based on the type of the arguments should be avoided. Otherwise weβre not doing generic programming but just overloading functions which is much more complicated.
With multiple dispatch, just as the meaning of a function is important to get consistent, itβs also important to get the meanings of the argument slots consistent when possible. Itβs not ideal for a function to treat an argument in a fundamentally different way based on its type: e.g. βif this is a Function, then call it on pairs of values, otherwise assume itβs a container providing the values to useβ.
I think in 2.0 it would be worth identifying where these are currently used and find other solutions where possible. There might be an exception for when one of the arguments is a type, since type arguments tend to play a special role, but this needs more analysis.
I think as a practical matter it might be ok to make an exception for constructors. For example, String:
String(v::AbstractVector{UInt8})
Create a new String object using the data buffer from byte vector v. If v is
a Vector{UInt8} it will be truncated to zero length and future modification
of v cannot affect the contents of the resulting string. To avoid truncation
of Vector{UInt8} data, use String(copy(v)); for other AbstractVector types,
String(v) already makes a copy.
When possible, the memory of v will be used without copying when the String
object is created. This is guaranteed to be the case for byte vectors returned
by take! on a writable IOBuffer and by calls to read(io, nb). This allows
zero-copy conversion of I/O data to strings. In other cases, Vector{UInt8}
data may be copied, but v is truncated anyway to guarantee consistent
behavior.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
String(s::AbstractString)
Create a new String from an existing AbstractString.
Itβs also totally possible to satisfy the βfully genericβ docs while adding valuable additional context that is useful for understanding. For example, the two extra length docstrings 100% satisfy the generic βnumber of elements in the collectionβ for both AbstractArray and String, but each adds extra helpful context (like that it is prodβsize for arrays and number of characters for strings).