Proposal: Introduce help catalogs

Well, still one more thing. I mean, not a big thing, and as you mentioned, it’s not something you really have to learn since it guides you to use it when needed, but my point is: why having this “one more thing” if we could make what we already have (the docs) dynamic to solve the problem?

Sorry if it was not clear. The function accepted in my example would either be a 0 or 1 arity function, and for 1-arity function it would receive the values of arguments. This could be used to make h Math.div(1, 0) print out some help about division by zero.

I mean, I know it may not be used that much, but still, it’s an improvement to the docs that makes it possible to do what you want for guards. Also, I know it creates problems to generate the online docs, since there will be multiple versions of it for the same function, but we could define that this 1-arity functions should always match to nil and print the details for all cases there, and send nil as this function when generating the online docs or when doing h Math.div.

Something like this:

defmodule Math do
  @doc &div_doc/1
  def div(left, right), do: left / right

  defp div_doc([_, 0]), do: "It's mathematically impossible to divide any number by `0`"
  defp div_doc(nil) do 
    """
    Divides `left` by `right`

    ## Observations

    - `right` should not be `0`: #{div_doc([nil, 0])}
    """
  end
end

EDIT: I know the example I created is not very good use case for this 1-arity function, and I really can’t think on a good one for it right now. So maybe only supporting 0-arity functions would be good enough, since it already covers everything you would be able to do with help catalogs, and we could leave the h Math.div(x, y) idea for another time. :wink:

I understand what you mean now - it would be repetition of the same big warning.

These ecto errors are the errors I imagine would be moved to the help catalog. I’d prefer to have them directly in the compilation result, but I guess it poses a problem when they keep appearing over and over.

1 Like

It’s already possible to include dynamic content in documentation, but it’s a bit tricky to do that since you need to ensure that whatever code you’re using is already loaded. This could get even more complicated in Elixir core because of bootstrapping, but correct me @josevalim, if I’m wrong.

Here’s an example:

defmodule DocHelpers do
  def list_guards do
    ~w(is_atom/1 is_binary/1 length/1)
  end
end

defmodule DynDoc do
  @doc """
  Guards

  Not all expressions are allowed in guard clauses.

  List of allowed expressions:

  #{Enum.map_join(DocHelpers.list_guards(), "\n", &"  * #{&1}")}
  """
  def guards, do: @doc
end

and the output:

iex(5)> h DynDoc.guards

                                  def guards()

Guards

Not all expressions are allowed in guard clauses.

List of allowed expressions:

  • is_atom/1
  • is_binary/1
  • length/1
1 Like

I kind of got used to them :slight_smile: Also the really long ones are shown only in cases when something is clearly wrong so they don’t show up that often. I see the point in shortening them to be able to show more info for more basic errors though, so :+1: to the proposed changes.

+1 this is really good

#1

I’m with @OvermindDL1 on this one :slight_smile:

I would also be nice to have a list of which warnings would be affected by this change, and how often long warnings appear in Elixir packages.

To be honest, I haven’t had that impression of “scary long warnings”.

#2

The first thing that caught my eye was how close the proposed syntax is to function calls in Erlang (i.e. os:system_time()). It’s just an anecdote, but it might be confusing for developers coming from that programming language.

As others has expressed, my main concern is how developers are going to provide this detailed explanations in their codebases.

Besides, this seems to be defined at application/project level. So, the best place would be the mix.exs file? I actually don’t think that should be the place, but what other alternatives there are? Aggregate the warning information of each module?

#3

I would expect --help to be the help of the elixir command, not related to documentation nor IEx helpers.

In addition, Elixir provides all the tools to build your own shortcuts for that:

elixir -e "require IEx.Helpers; IEx.Helpers.h Map"

You can define your own scripts based on that, for example iexh, iexb and iext (which will be shorter than their elixir --help, elixir --behaviour-help, and elixir --type-help counterparts):

#!/bin/sh

# Usage: iexh Map
elixir -e "require IEx.Helpers; IEx.Helpers.h $1"

# Usage: iexb GenServer
elixir -e "require IEx.Helpers; IEx.Helpers.b $1"

# Usage: iext Calendar.iso_days
elixir -e "require IEx.Helpers; IEx.Helpers.t $1"

Edit: I think the commands above won’t work with external packages :sweat_smile:

1 Like

I think those are runtime errors.

However, this comment opens a new question: would the help catalog be restricted to warnings, or exceptions/errors or any kind of hint would be permitted?

1 Like

Yeah, but that is executing on compilation time, by assigning a function to @doc this function would execute on runtime.

I’d like to add another vote for this. I think that moving to “short” messages by default will be harmful for new developers.

Take this warning from the original post:

warning: def foo/2 has multiple clauses and also declares default values. In such cases, the default values should be defined in a header. Instead of:

    def foo(:first_clause, b \\ :default) do ... end
    def foo(:second_clause, b) do ... end

one should write:

    def foo(a, b \\ :default)
    def foo(:first_clause, b) do ... end
    def foo(:second_clause, b) do ... end

  iex:4

If that was shorted to warning: def foo/2 has multiple clauses and also declares default values. Please define default values in a header (elixir --explain defaults_and_clauses) by default it is much less helpful (and friendly!) for someone just starting with Elixir. I know it’s more work but I’d prefer to have short, medium, and long versions of warnings. The short version would basically just be a title, in this case perhaps: def foo/2 has multiple clauses and also declares default values. Please define default values in a header.

Then the medium version (which is by default printed out for compilation warnings) would start with the exact same title and follow it up with the expanded information that we currently have now. And the long version would build upon that with yet more information, sometimes it would just append information to the medium version, but it could also optionally re-write the medium version completely (but keep the same title).

I want to note that using short warnings by default also makes it more difficult to access the expanded information depending on way that you are viewing the error. For example if the error is in your test output on CI you actually be on a computer where you cannot just easily run elixir --help cmd. This is another reason it would be nice if the detailed warning information was available online.

1 Like

Thanks everyone for the feedback.

I will archive this proposal for now. It seems that the majority of developers prefer the long warnings and, if short warnings will be opt-in, it will likely be used only by a few and therefore I do not see the benefits of adding this complexity to the tool chain.

13 Likes