Proposal: Introduce help catalogs

official-proposal

#23

I think to strike a balance with 3 levels of warnings would be like so:

  • short - warnings that are emitted now, short and clean
  • long - warning with max 2 paragraphs, enough to give options or hints how it can be fixed
  • detailed - more than long: references to other functions or modules, links,
    maybe some examples “do this, don’t do that”, basically connect warnings with docs.
    So when you have unused variable warning I’d go with something like
warning: variable "unused" is unused (did you mean `_unused` ?)

or

warning: variable "unused" is unused, add leading underscore to mark it if you're not using it intentionally.

if you have for example clauses warning:

iex(4)> defmodule Foo do  
...(4)> def f(_), do: 1 
...(4)> def f(2), do: 2
...(4)> end
warning: this clause cannot match because a previous clause at line 5 always matches

you can make it like so:

warning: this clause cannot match because a previous clause at line 5 always matches
Rearrange clauses to match arguments gradually, more info at <catalog help page>

or something in this spirit.

That’s my thoughts, hopefully it helps somehow.

BTW: is there a way to peek warnings strings in Elixir code? I’m not very experienced and couldn’t find them anywhere.


#24

Thanks, that helps to see what you mean. But I think we can strike a balance between short and medium, in a way we don’t need to add even more options, and still keep warnings relevant. We have been mostly fine with one so far and having two should be an improvement, we probably don’t need a third. :slight_smile:


#25

Just as a note, I love this! Never let warnings be disableable! :slight_smile:

/me wonders if there is a way to pass the warnings_as_errors flag in mix.exs…

Overall though, I like the verbose warnings. It is both telling you what is wrong and often how to fix it, but also is a big thing of “You Need To Stop Doing This Warning Thing”.

But a help catalog could be quite, just as long as the big warnings by default don’t stop. :slight_smile:

Honestly… Yes by default, it makes it far less ignorable! :slight_smile:


#26

Q1

A help catalog with a more detailed information about the warning would be a great addition.

Q2

Making help and warning info accessible via the elixir command would be another great addition.

Q3

This would be the natural progression of 2, so I vote for this too.

One suggestion I have is to not restrict the help catalog to just warnings. It would be great if we could expose other kinds of useful information which is usually shoved into a README or Getting Started guide via the help catalog. Take distillery for example, it would be great if we could list all the topics in a help catalog for distillery by something like elixir -h 'distillery:*' and then get detailed info on elixir -h distillery:getting_started or elixir -h distillery:phoenix.


#27

This is a potential source of confusion but I don’t believe the catalog would work as a general pages or guides. I don’t believe the command line is a rich enough environment for such material.


#28

We may use URLs and point them to an online version hosted at elixir-lang.org. Sure, this may add some burden when releasing Elixir and making sure there’s a new help catalog online, but nothing beats URLs at being easy to access.

Still not sure with lib’s catalog though. :thinking:


#29

I think we should be able to integrate the catalog with ExDoc, which means both Elixir and libraries should have online versions of those. But I’d personally prefer a flow that works on the command line (or from my editor) and does not require a browser. Thoughts?


#30

I think this is a good enough reason to go with the functions based approach that you’re proposing.

This got me thinking. Isn’t accessing the catalog with help placing it in the same frame of reference from the perspective of the user as documentation and guides? We’re even calling it help catalog, but the purpose seems to be limited to explaining warnings and errors in more detail. I don’t think it should work as a general pages or guides collection either, but in that case we should probably go with a different name that clearly defines the purpose. I think we should also reconsider --explain.

Regarding naming, I don’t like the sound of “error catalog” or “warning catalog”, so I did a quick thesaurus lookup on “warning” and these jumped out at me: notice, advice, advisory, hint. I like the sound of “advice” or “hint” and think that they communicate the purpose better. elixir --hint elixir:nested_var? I think this also helps newcomers understand what this command means in a warning message.

The only issue I have with the above is that it moves this farther from documentation as the storage and retrieval mechanism. On one hand we can use this information to compile a list of hints (just trying to see how it sounds) in ExDoc. That makes it useful to Elixir core and libraries. We could then solidify the convention, and popularise it in other Erlang VM languages to let them benefit as well. Or we could rely on the docs chunk which is already making its way into those languages. It seems though that the docs chunk approach would prevent dynamic content. Isn’t there a way to have the best of both worlds? I have a half baked solution in mind, but will sleep on it before I write it up.


#31

I’m totally pro having shorter warnings for stuff like unused variable x, but having an easy way for newcomers to get more info about it. But like @lackac, I’m not so sure about the naming after reading all the discussion. So not so sure, if I like the overall idea of a separated feature for it.

Good enough. But:

  1. Please export it to the docs either under a centralized page, or in sections inside the modules, or even both.
  2. Using functions to define this is quite strange. I mean, I know it makes it a lot more powerful, since one can use runtime stuff, but I don’t really think we need this, since as you mentioned: contextual information should remain as part of the warning, and if it’s runtime stuff, it’s somehow contextual.

Yes, sure.


Now, with my considerations for each question in mind, what about doing something like:

defmodule Foo do
  @moduledoc "Foo doc"

  @doc """
  Foo.bar doc

  # Warnings

  ## Put some warning title here

  And its description.

  ## Put another warning title here

  And more description.
  """
  def bar do
    ...
  end 
end

And of course, elixir -h app:Foo.bar would totally do the job, you should only link to them on the documentation.

If one would like to separate warnings, like you want to do to core, it’s always an option to create a warning function like:

defmodule Foo do
  @moduledoc "Foo doc"

  @doc """
  Foo.bar doc. 

  Please notice this function can throw `warning1` and `warning2`.
  """
  def bar do
    ...
  end 

  @doc """
  # Warning title here

  And its description.
  """
  def warning1, do: warn("this")

  @doc """
  # Another warning title here

  And more description.
  """
  def warning2, do: warn("that")
end

PS.: for warnings thrown by erlang code, you could create a CompilationWarnings module with the functions and docs for each of them and raise an exception if they are called directly.

This would use the stuff we already have, so no complexity added to the core, and it is a very plausible solution for the problem you have IMO. And with this solution in mind, my final answers to the questions would change to:

  1. Q: What do think about the idea of supporting help catalog in general?
    A: The idea of reducing noise on warning is really good, but we can solve it without a new catalog feature
  2. Q: What do you think about the suggested syntax for catalogs and its implementation?
    A: Well, right now IMO we should not implement it at all
  3. Q: Should we close the gap and allow help and open generally available in the elixir and mix run commands?
    A: That’s now a really big YES, we should focus all the big efforts on these stuff.

So what do you think?


#32

Being able to access documentation directly from IEx is useful to me. It allows me to not get stuck on a problem while coding when I have no internet access (and also access from the terminal is just quicker and more convenient IMO). I would not like to see an error message, which I don’t fully understand, and then have to rely on internet access to view the detailed description.


#33

We need to balance the confusion of also using help for catalog with the confusion of introducing a third option. Today we already have two commands: help and info. I don’t know the correct answer, but I have a feeling introducing a third command will just be too confusing.

To be very fair, there is nothing stopping someone from adding “distillery:phoenix”. Elixir will have a “elixir:guards” and we do have a page named “Guards”. Implementation wise you could just File.read!(...) the page contents. I just wouldn’t expect packages to necessarily do this because pages just look superior on ExDoc thanks to navigation, accessibility tools, hopefully search, etc.

I have mentioned on other replies why I don’t think moving this to the documentation is a good idea, so please check them and follow back. But in a nutshell, there is more dynamic information than just the context of the warning.


#34

“Yes” to all the questions.

I really like how Credo does this: shows you one-liners with an option to print a very detailed information about each one. My only gripe is that one needs to copy-paste the whole location (filename + line number) into the command line to get the details. It would be better to just number the warning so that it’s easier to type in the command that prints the explanation for a specific occurrence. I’d also rather have the hint about --explain appear only once. Summing up, something along the lines of:

Compiling 1 file (.ex)
warning #1: variable "thing" is unused
  lib/file.ex:8

warning #2: function MyModule.next/1 is undefined or private. Did you mean one of:

      * next/2

  lib/other.ex:98

Compilation finished with warnings. Use "mix explain" to get a detailed explanation or "mix explain #2" to explain a specific warning.

As for the short/medium/long thing, I would like something like the following rule: by default print the “what”, “where” and contextual hints for fixing; put the “why” and general hints/explanation in the catalog. In the example above, I’d rather have the notice about prefixing the variable with _ in the catalog since an unused variable might be because of a typo or incomplete refactoring. On the other hand, I’d like to keep the suggestions for the second error since this is useful for fixing the problem.

I find myself doing the same. My only concern is: how/will this work for deps? It would make sense to put it in the context of Mix, so something like mix explain app:entry.


#35

Hey, not really a proposition, but just something comes to my mind. Imagine something like cache enhancement for help catalogs. Such cache could store type, app and context-based data. It could be even disabled by default and cached data would change when project is recompiling. Then we could have two ways to cache:

  1. automatic (default if caching enabled) - instead of focusing on specific named warning we could just browse them separately one after another

  2. manual - display short warning and ask to cache it every time
    If enabled developer could name specified cache, so it could be fetched easily

I don’t even know if that requires lots of work or no. Just it’s what my crazy brain visualized. :smiley:


#36

I’d like a help catalog, but I do not think it would be helpful to new developers. I gained an understanding of Elixir partly because the warnings were clear and obvious and sometimes verbose. It can be a frustrating experience to get a bunch of brief errors and then have to refer to another source of information. It can also be exhausting and make a developer want to give up. This is part of the friction I got when starting with Erlang – the errors were often too brief and hard to interpret.

Instead, I think

Long warnings should be opt out, not opt elsewhere

The long warning should be opt out. Seasoned developers can put the relevant config options in mix.exs. Or something like elixirc --brief-warnings. I do like the idea of this proposal, but I also know that there is a scare factor for new developers seeing a warning like:

warning: def foo/2 has multiple clauses and also declares default values (elixir --explain default_values)

Then running that command and seeing a whole page of details about function heads, but not seeing it in context. This can be really exhausting to have to switch between the compiler output and then referring to another source of information.

In general, yes to help catalogs

Yes to help catalogs, but with the caveat that the long warnings are there by default and can be shortened with an option like --brief-warnings.

The syntax and formats proposed are reasonable to me.

Another problem is having to write two different versions of the warning, but that would happen with my suggestion and with the catalog.


#37

I see. Sorry for repeating it then, but I still think help catalogs is just one more thing to learn for newcomers. Why not dynamic docs in the following style:

@doc &guards_doc/0
def guards(left, right), do: ...

defp guards_doc() do
  # Here you extract the guards doc
end 

That would even make something like this possible:

defmodule Math do
  @doc &add_doc/1
  def add(left, right), do: ...

  defp add_doc(nil), do: "Adds `left` to `right`."
  defp add_doc([left, right]), do: "Adds `#{left}` to `#{right}`."
end

h Math.add
=>                               def add(left, right)
=>
=> Adds `left` to `right`.

h Math.add(1, 2)
=>                               add(1, 2)
=>
=> Adds `1` to `2`.

Any thoughts?


#38

A good example of it’s use would be:

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(_), do: "Divides `left` by `right`"
end

#39

Anyways, I see how my proposal it’s kind of deviating from the original solution, but I’m just trying to think a suitable solution for the same problem without having to add a new entity like “catalogs” to the table. So please, critics are really welcome, maybe I’m not thinking in the problems my solutions might cause, or maybe there is something I’m missing…


#40

Is there any way to test this? Do you have knowledge of how other compilers approach this topic? If I recall, Rust was mentioned on IRC as a good example, but I don’t think they provide documentation access from CLI.

My feeling is that presenting the command in the warning message where you can just copy-paste from makes it very friendly to newcomers regardless of the name of the option.

On the other hand, I understand that you are trying to limit the number of options people need to learn to be productive with the CLI and IEx. However, we already have h, b, t, and i in IEx instead of a single help that covers all. To that end, would it be worth considering unifying them? I realise they are separate because of the ambiguity of h Module.something. Are you interested in a function? Callback? Type? Do you want information on the return value of Module.something()? If we remove i from the equation we could handle all of those cases intelligently. h could query all of functions, callbacks and types and list all of them. With the same syntax as in docs the result could be more focused (h c:GenServer.handle_call). There are some unanswered questions here, like how to list callbacks and types, but since this is starting to get off topic I’m going to stop here. @josevalim, feel free to move this bit to a new thread if you think it’s worth considering.

Going back to the question of the catalog. If we unify h, b, and t, then I think it makes sense to pull the catalog functionality under the same roof too. Otherwise I’d follow the same pattern as with those helpers and differentiate on the command level instead of the argument level.


#41

There have been two examples mentioned as the purpose of dynamic content:

  1. For including contextual information
  1. For simply generating otherwise static information

Those are both useful when invoking the catalog from the command line. As part of documentation generated by ExDoc the dynamic part of the information becomes less useful. In the first case it’s because the referenced configuration value could be out of date. In the second the listing could be replaced by a link to the relevant page in the docs.

In order to get the best of both worlds I think we can follow @josevalim’s implementation proposal of using functions, but introduce the convention of using the function’s docs for the static parts:

defmodule HelpCatalog do
  @doc """
  Guard functions

  Not all expressions are allowed in guard clauses, but only a handful of them. This is a deliberate choice. This way, Elixir (and Erlang) can make sure that nothing bad happens while executing guards and no mutations happen anywhere. It also allows the compiler to optimize the code related to guards efficiently.

  Visit [Guards guide](/guards.html) to learn more.
  """
  def guards do
    all_guards =
      fetch_all_guards_from_docs()
      |> Enum.map_join("\n", &"  * #{&1}")
    @doc <> "\n#{all_guards}"
  end
end

This way there is no extra work to make this part of ExDoc generated content, and the static parts even become part of the docs chunk. Another added benefit is that it becomes easier to maintain the content as it’s syntax highlighted as markdown by the editor. :slight_smile:


#42

Thanks for the input @voughtdq. It is really nice to hear that Elixir warnings have been helpful. I have explained in previous replies why long warnings should need to be opt-in. Can you please see the previous comments and reply accordingly? In a nutshell, once the catalog is present, the warnings will tend to get much longer.

How is it one more thing to learn? The only time they will have to care about it, we will explicitly tell them which command to run. It is not like they have to memorize it. :slight_smile:

In your example, such as div_doc, when would div_doc ever be called with zero? What is passing those inputs in? How would ExDoc know how to generate documentation from now on?

I would say that unifying them is welcome. We recently got rid of s exactly because of this. But, as you said, I don’t know how to do it without adding ambiguity. Note that the arguments of h have to be valid Elixir syntax and c:GenServer isn’t (it is, but it emits warnings).