Julia: help should indicate where things come from

Created on 30 Sep 2016  路  22Comments  路  Source: JuliaLang/julia

Currently there's no indication in help output where a binding comes from and this information is fairly annoying to find.

Hacktoberfest docsystem good first issue help wanted

Most helpful comment

I don't want to stop the conversation, but let me try to sum up what we have so far:

  1. We want people to use methods, fieldnames, and which. The help?> prompt should do one thing well, and that thing is to view the doc strings.

Proposal: don't add definition or documentation location information to the help output.

  1. It is hard to find where the doc strings are. This prevents people from contributing to them.

Proposal: add a new function that aids in finding the location of doc strings.

I think I'd mimic which and MethodList. The new function would return a DocStrList which would contain the matching DocStr objects. Then I'd define show on DocStrList. I'd probably have to define show on DocStr, too.

  1. It would be nice to know which module methods are defined in.

Proposal: add the module name to the output of showing Method objects. Maybe something like this:

julia julia> @which sin(1.0) sin(x::Float64) in Base.Math at math.jl:260

All 22 comments

By "where", you mean what module? Or the pathname?

Module. Source file wouldn't hurt, but isn't currently recorded much of the time.

Some breadcrumbs for anyone wanting to implement this: module, file, and line number are stored in the .data field of all DocStr objects.

help?> Docs.DocStr
  Docs.DocStr

  Stores the contents of a single docstring as well as related metadata.

  Both the raw text, .text, and the parsed markdown, .object, are tracked by this
  type. Parsing of the raw text is done lazily when a request is made to render
  the docstring, which helps to reduce total precompiled image size.

  The .data fields stores several values related to the docstring, such as: path,
  linenumber, source code, and fielddocs.

julia> ans.meta[:results][1].data
Dict{Symbol,Any} with 6 entries:
  :linenumber => 117
  :typesig    => Union{}
  :fields     => Dict{Any,Any}()
  :path       => "docs/Docs.jl"
  :module     => Base.Docs
  :binding    => Base.Docs.DocStr

That's helpful 鈥撀爏eems like we could indicate all of that information then.

Yeah, everything is there, all used by Documenter in one way or another. The "line range" can also be computed based on the raw docstring text, if we want to display that rather than just the starting line.

I'm interested in taking this on as my first issue.

How should I format this information? As an example, I could just append it to each doc entry:

help?> sum
search: sum sum! sumabs summary sumabs2 sumabs! sum_kbn sumabs2! cumsum cumsum!

  sum(f, itr)

  Sum the results of calling function f on each element of itr.

  Defined in module Base, file reduce.jl.

  sum(itr)

  Returns the sum of all elements in a collection.

  Defined in module Base, file reduce.jl.

  # etc...

Also, the line number is the first line of the documentation, not the first line of the object being documented. We'd want to have the latter printed in the help output, right? Should I just not include line number? Or is there another way of finding the line number for the definition itself?

Should I just not include line number? Or is there another way of finding the line number for the definition itself?

For functions there is functionloc

For everything else, you could guess the line number by using the line number of the documentation start plus the number of newline characters in the documentation, plus 1.

I'ld like it if types and variables and everything else also was annotated with line numbers.
Though if for every variable (rather than just globals) this would get kinda expensive for memory use.

It seems that the best I can do is to give the module, file, and line number of the documentation itself.

Consider the following:

help?> +
search: + .+

  +(x, y...)

  Addition operator. x+y+z+... calls this function with all arguments, i.e.
  +(x, y, z, ...).

So there is a single documentation entry, even though we have many methods for +.

So what if it outputs this:

help?> +
search: + .+

  +(x, y...)

  Addition operator. x+y+z+... calls this function with all arguments, i.e.
  +(x, y, z, ...).

  Documented in module Base, file docs/helpdb/Base.jl, line 3003.

Or am I missing something?

For non-documented functions we print the method list.

help?> Core.Inference.setindex!
  No documentation found.

  Core.Inference.setindex! is a Function.

  # 14 methods for generic function "setindex!":
  setindex!(t::Core.Inference.ObjectIdDict, v::ANY<:Any, k::ANY<:Any) at dict.jl:366
  setindex!(A::Array{Any,N<:Any}, x::ANY<:Any, i::Int64) at essentials.jl:117
  setindex!{T}(A::Array{T,N<:Any}, x, i1::Real) at array.jl:453
  setindex!{T}(A::Array{T,N<:Any}, x, i1::Real, i2::Real, I::Real...) at array.jl:454
  setindex!{T}(A::Array{T,N<:Any}, X::Array{T,N<:Any}, I::Core.Inference.UnitRange{Int64}) at array.jl:482
  setindex!(A::Array, X::AbstractArray, I::AbstractArray{Int64,1}) at array.jl:465
  setindex!(A::Array, x, I::AbstractArray{Int64,1}) at array.jl:458
  setindex!{T}(A::Array{T,N<:Any}, X::Array{T,N<:Any}, c::Core.Inference.Colon) at array.jl:492
  setindex!{T,N}(A::Array{T,N}, x::Number, ::Vararg{Core.Inference.Colon,N}) at array.jl:501
  setindex!(A::Array, x::Number, ::Core.Inference.Colon) at array.jl:500
  setindex!(A::AbstractArray, v, I...) at abstractarray.jl:891
  setindex!{K,V}(h::Core.Inference.Dict{K,V}, v0, key::K) at dict.jl:748
  setindex!{K,V}(h::Core.Inference.Dict{K,V}, v0, key0) at dict.jl:740
  setindex!(t::Core.Inference.Associative, v, k1, k2, ks...) at dict.jl:330

It's unclear what exact should be included in the doc though. (You almost certainly don't want to unconditionally include the list of all methods).

Maybe we should only print the name of the module where the _function_ (not methods) is defined? I don't think printing the file and line number is a good idea: these are useful only for people hacking these functions, not to its users. methods is there for you if you want this information.

I don't think printing the file and line number is a good idea: these are useful only for people hacking these functions, not to its users.

Indicating the file and line number of the docstring would be useful for any user that happens to notice a typo in the docs and wants to contribute a fix, not just for people hacking on the actual implementation.

Documented in module Base, file docs/helpdb/Base.jl, line 3003.

Regards to formatting: perhaps follow the format used elsewhere for file/line numbers, foo.jl:100 rather than foo.jl, line 100?

OK, now I have:

help?> +
search: + .+

  +(x, y...)

  Addition operator. x+y+z+... calls this function with all arguments, i.e.
  +(x, y, z, ...).

  Documented in Base at docs/helpdb/Base.jl:2998.

But now I've been thinking about this some more... We already have a good set of tools (methods, which) for finding where methods are defined. And we can find what module variables (including types and function objects) are defined in using which, too. So do we want to integrate this information into the help output, at the risk of cluttering up the output? Or are we really just wanting a way to find where the doc strings are?

If it is the latter, maybe we don't want to change the help output at all, but rather create a new function, whichdoc or something like that, which tells you where all the doc strings are located.

So do we want to integrate this information into the help output, at the risk of cluttering up the output?

IMO, Yes. Yes we do.
If I am opening the help I always want a method list.
I always want to know the _module name_ and the various different _methods_, or _fields_ if it is a type.

I don't always want the line number, or the filename -- not when the docs are actually good.
If they are not good, then I want the filename and line number, so I can find and read the code to work out exactly what it does.

If I am opening the help I always want a method list.

Are you _really_ sure? Compare the readability:

help?> abs
search: abs abs2 abspath abstract AbstractRNG AbstractFloat AbstractArray

  abs(x)

  The absolute value of x.

  When abs is applied to signed integers, overflow may occur, resulting in the
  return of a negative value. This overflow occurs only when abs is applied to
  the minimum representable value of a signed integer. That is, when x ==
  typemin(typeof(x)), abs(x) == x < 0, not -x as might be expected.

vs:

julia> methods(abs)
# 28 methods for generic function "abs":
abs(x::Bool) at bool.jl:38
abs(x::Float16) at float.jl:488
abs(x::Float32) at float.jl:489
abs(x::Float64) at float.jl:490
abs(a::Base.Pkg.Resolve.VersionWeights.VWPreBuildItem) at pkg/resolve/versionweight.jl:97
abs(a::Base.Pkg.Resolve.VersionWeights.VWPreBuild) at pkg/resolve/versionweight.jl:150
abs(a::Base.Pkg.Resolve.VersionWeights.VersionWeight) at pkg/resolve/versionweight.jl:206
abs(a::Base.Pkg.Resolve.MaxSum.FieldValues.FieldValue) at pkg/resolve/fieldvalue.jl:64
abs(f::Array{Base.Pkg.Resolve.MaxSum.FieldValues.FieldValue,1}) at deprecated.jl:49
abs(::DataArrays.NAtype) at /home/milan/.julia/DataArrays/src/operators.jl:418
abs(x::Unsigned) at int.jl:98
abs(x::Signed) at int.jl:99
abs(x::Real) at number.jl:66
abs(z::Complex) at complex.jl:141
abs{T}(a::Base.Pkg.Resolve.VersionWeights.HierarchicalValue{T}) at pkg/resolve/versionweight.jl:69
abs{T<:Base.Dates.Period}(a::T) at dates/periods.jl:112
abs(A::SparseMatrixCSC) at deprecated.jl:49
abs(B::BitArray) at deprecated.jl:49
abs(M::Bidiagonal) at deprecated.jl:49
abs(D::Diagonal) at deprecated.jl:49
abs(M::Tridiagonal) at deprecated.jl:49
abs(M::SymTridiagonal) at deprecated.jl:49
abs(x::AbstractSparseArray{Tv<:Any,Ti<:Any,1}) at deprecated.jl:49
abs{T<:Complex{T<:Real}}(d::DataArrays.DataArray{T,N<:Any}) at /home/milan/.julia/DataArrays/src/operators.jl:202
abs{T<:Number}(d::DataArrays.DataArray{T,N<:Any}) at /home/milan/.julia/DataArrays/src/operators.jl:202
abs{T<:Complex{T<:Real}}(adv::DataArrays.AbstractDataArray{T,N<:Any}) at /home/milan/.julia/DataArrays/src/operators.jl:212
abs{T<:Number}(adv::DataArrays.AbstractDataArray{T,N<:Any}) at /home/milan/.julia/DataArrays/src/operators.jl:212
abs{T<:Number}(x::AbstractArray{T,N<:Any}) at deprecated.jl:49

I'd rather you make the effort of typing methods manually rather than cluttering the output for all users.

We definitely shouldn't be printing out method lists as well. Some of the longer ones are going to distract from the _main_ content of the docs — the description and examples. As @nalimilan pointed out, just use methods when all that information is actually needed, it'll get annoying quickly if every time you do help?> convert the real docstring gets overshadowed by a 622 long (currently) list of methods.

I suspect that a better way for readers to get a feel for what methods are associated with a particular docstring is via appropriate doctests that _show_ how to use the different methods rather than just _list_ them.

(If an author wants auto-generated method lists here and there in their package they are already available in DocStringExtensions.jl along with a few other helpful things.)

I'd rather you make the effort of typing methods manually rather than cluttering the output for all users.

You are correct. I can define my own help macro that does both.
I really should.

help?> convert the real docstring gets overshadowed by a 622 long (currently) list of methods.

You are right about that.
Most things have dozens, but there are a few with hundred.
And we really want explain those semantically

Ok I am convinced


This has gotten me thinking though:

We can do a lot better at printing a method list in a end-user friendly way (as compared to developer/package developer friendly way)

Consider if we break it down by subfolder of Base and Subfolder of PkgDir, (this assumed we are talking about a function, which means it is from the same Module -- since it wouldn't be the same function if it was from a different module.)
and get rid of anything from deprecated.jl
and list the originating module of the function first:

Base:
-----
abs(x::Bool)
abs(x::Float16)
abs(x::Float32)
abs(x::Float64)
abs(x::Unsigned)
abs(x::Signed)
abs(x::Real)
abs(z::Complex)

Base.Dates:
-----------
abs{T<:Base.Dates.Period}(a::T)

Base.Pkg:
---------
abs(a::Base.Pkg.Resolve.VersionWeights.VWPreBuildItem)
abs(a::Base.Pkg.Resolve.VersionWeights.VWPreBuild)
abs(a::Base.Pkg.Resolve.VersionWeights.VersionWeight)
abs(a::Base.Pkg.Resolve.MaxSum.FieldValues.FieldValue)
abs{T}(a::Base.Pkg.Resolve.VersionWeights.HierarchicalValue{T})

DataArrays:
-----------
abs{T<:Complex{T<:Real}}(d::DataArrays.DataArray{T,N<:Any})
abs{T<:Number}(d::DataArrays.DataArray{T,N<:Any})
abs{T<:Complex{T<:Real}}(adv::DataArrays.AbstractDataArray{T,N<:Any})
abs{T<:Number}(adv::DataArrays.AbstractDataArray{T,N<:Any})
abs(::DataArrays.NAtype)

That is a slightly different issue though.

I don't want to stop the conversation, but let me try to sum up what we have so far:

  1. We want people to use methods, fieldnames, and which. The help?> prompt should do one thing well, and that thing is to view the doc strings.

Proposal: don't add definition or documentation location information to the help output.

  1. It is hard to find where the doc strings are. This prevents people from contributing to them.

Proposal: add a new function that aids in finding the location of doc strings.

I think I'd mimic which and MethodList. The new function would return a DocStrList which would contain the matching DocStr objects. Then I'd define show on DocStrList. I'd probably have to define show on DocStr, too.

  1. It would be nice to know which module methods are defined in.

Proposal: add the module name to the output of showing Method objects. Maybe something like this:

julia julia> @which sin(1.0) sin(x::Float64) in Base.Math at math.jl:260

That is a slightly different issue though.

@oxinabox, yes probably worth it's own issue (or PR) to discuss improvements to Docs.summarize output if you'd like.

The help?> prompt should do one thing well, and that thing is to view the doc strings.

Agreed, no need to try to pack too much info into one command, though I do think a single line pointing to the source location would be subtle enough for inclusion in the help?> output. Would probably also help make a clear separation between each docstring when several are concatenated: * for example:

help?> *
search: * .*

  *(s::AbstractString, t::AbstractString)

  Concatenate strings. The * operator is an alias to this function.

  julia> "Hello " * "world"
  "Hello world"

  *(x, y...)

  Multiplication operator. x*y*z*... calls this function with all
  arguments, i.e. *(x, y, z, ...).

  *(A::AbstractMatrix, B::AbstractMatrix)

  Matrix multiplication.

Listing the source between each one with some kind of separator might look alright:

help?> *
search: * .*

  *(s::AbstractString, t::AbstractString)

  Concatenate strings. The * operator is an alias to this function.

  julia> "Hello " * "world"
  "Hello world"

  strings/basic.jl:95
  --------------------------------------------------------------------

  *(x, y...)

  Multiplication operator. x*y*z*... calls this function with all
  arguments, i.e. *(x, y, z, ...).

  docs/helpdb/Base.jl:2501
  --------------------------------------------------------------------

  *(A::AbstractMatrix, B::AbstractMatrix)

  Matrix multiplication.

  linalg/matmul.jl:127
  --------------------------------------------------------------------

Proposal: add a new function that aids in finding the location of doc strings.

Like functionloc perhaps (docstrloc), which returns a 2-tuple of (path to file, linenumber). A whole new type for this doesn't seem necessary since (most of the time) it would be a list of length 1.

Making which/@which, edit/@edit, methods, fieldnames, and friends (?) more discoverable (particularly in the process of seeking help/documentation) might be worthwhile?

Thanks for all of this discussion, everyone. I hope I'm not dragging this out too much.

Like functionloc perhaps (docstrloc), which returns a 2-tuple of (path to file, linenumber).

I have something along those lines working now...

julia> Docs.docstrloc(sum)
3-element Array{Tuple{String,Int32},1}:
 ("reduce.jl",332)
 ("reduce.jl",339)
 ("docs/helpdb/Base.jl",1276)

A whole new type for this doesn't seem necessary since (most of the time) it would be a list of length 1.

My original thinking was to create a new function to output the doc strings with the location annotation (much like your example), and then leave the help output alone. It just doesn't seem like the general user needs to know where the doc strings are located. The new type I was talking about was just so that I could make a method show(io, d::DocStrList) that would insert the dividers between each DocStr.

The nice thing about an annotated output (whether by default or as a new function) is that you can see the content of the doc string. The output of docstrloc is concise, but lacks that context. But maybe there's a place for both.

Related method signature discussion: #13504

I finally got around to making a PR for adding docstrloc, assuming we still like that idea. See #22972.

Was this page helpful?
0 / 5 - 0 ratings