What does 'use Mix.project' mean

I’m trying to understand the mix documentation at

The 2’nd line of mix.ex says

use Mix.Project

The elixir documentation says that use is a macro that expands into

 require Mix.Project
 Mix.Project.__using__()

The documentation says that I need to call require in-order to evoke macros in
Mix.Project.

I assume that using() is a macro in Mix.Project but what does it do? and where can
I find the definition?

The elixir documentation for use

says

Since use allows any code to run, we can’t really know the side-effects of using a module without reading its documentation. For this reason, import and alias are often preferred, as their semantics are defined by the language.

I’m rather confused by the fact that the 2’nd line of the first example (which should be
simple) uses a practice that is not preferred.

I’ve also observed that many module happily call use Mod but it is unclear why and
what the consequences of this will be – what do people do when reading unfamiliar code and find a use declaration? - how do they know what will happen to their code, since it appears that what you see will not be what you get (ie a macro has done something to their code)

7 Likes

You will need to read the code.

Everytime a module’s API is use, the effects are never clearly documented, so reading the code is our only hope.

use Mix.Project pushes the project to the stack of projects so that it is available to Mix. import and alias are usually preferred, but they can’t do what macros can do so all uses of use can’t be replaced with them.

I don’t think use Mix.Project is documented anywhere, but I am not sure what the documentation should be, maybe: “add this to your projects, so that Mix can find and use it”?.

3 Likes

I wouldn’t say they are never documented, but they can easily be missed when documenting the code. They should be documented in the module docs, and if they aren’t then I would call it a documentation bug.

4 Likes

The definition of Mix.Project.__using__/1.

I assume the example is so simple to the point it is not a best practice would be so this section of the documentation does not need to try and explain fully what the macro system is capable of. Maybe adding a link to the macro documentation could help though?

1 Like

@joeerl you can find more comprehensive documentation of what use does in it’s docs: iex> h use (same docs as on https://hexdocs.pm/elixir/Kernel.html#use/2). You can use the h helper even for stuff like: iex> h <<>> or h Kernel./ (needed Kernel. because h / is not a valid expression)

When reading examples I like to go through them line at a time understanding every line, so I think simple examples should be just that (simple) and not evoke macros which can do anything to my code.

1 Like

How can I find the documentation for the using macro in Mix.Project ?

Can I see this in the shell?

In this case it isn’t an example, it shows you how to define a mix project, I’m not sure any other way to go around showing how to define a project. Since defining a mix project is one of the first you do when you start out with elixir maybe we should make it easier to understand what goes on behind the scenes. But if we replace use Mix.Project with what it expands to:

@after_compile Mix.Project

@doc false
def __after_compile__(env, _binary) do
  push(env.module, env.file)
end

Then I think we are just exchanging one question for many others.

1 Like

You could by typing: iex> h Mix.Project.__using__ but since in this particular case it’s marked as @doc false [1] there are no docs. I’ve seen folks sometimes documenting __using__ macro on their module though.

[1] https://github.com/elixir-lang/elixir/blob/v1.7.3/lib/mix/lib/mix/project.ex#L93

The documentation is in the module docs for Mix.Project you can find it in the shell by calling h Mix.Project. The docs say that use Mix.Project is used to define the project, and I’m not sure the documentation should explain what goes on behind the scenes, that’s an implementation detail of mix itself.

I would have thought documenting macros was more important than documenting
functions (since macros change the meaning of your code)

So how can I

1) easily find the source code for a given module (given I know the module name)?
2) easily display the documentation for a function or macro if I know the name?

Can I do these in the shell?

Before being accepted for production use should not all Modules be required to produce some form of documentation for all exported functions and macros?

1 Like
  1. In the shell open Mix.Project
  2. In the shell h Mix.Project or h Kernel.spawn

You can also usually find the source code of modules and functions by following the </> link at the top-right corner of generated html docs, for example: https://hexdocs.pm/elixir/Kernel.html#spawn/1 links to https://github.com/elixir-lang/elixir/blob/v1.7.3/lib/elixir/lib/kernel.ex#L852.

1 Like

I think it would be ok for the documentation to say just do it and don’t worry
or has a few side effects so the system knows how to build a project

Some things you need to worry about and others you can just do - so it’s nice to know
which is which.

open Mix.Project

Could not open: "/private/tmp/elixir-20180806-3431-tbw4hz/elixir-1.7.2/lib/mix/lib/mix/project.ex". File is not available.

Aggggghhhh

1 Like

I view it as a reflection of a Don’t Write Macros But Do Learn How They Work culture.

i.e. in your software don’t lean too heavily on the mechanics of use but expect to use it in combination with the core products (to inject code at compile time).

For example Phoenix.Controller et al rely heavily on it. First use Phoenix.Controller appears in the seed rumbl_web.ex:

defmodule RumblWeb do
  @moduledoc """
  The entrypoint for defining your web interface, such
  as controllers, views, channels and so on.

  This can be used in your application as:

      use RumblWeb, :controller
      use RumblWeb, :view

  The definitions below will be executed for every view,
  controller, etc, so keep them short and clean, focused
  on imports, uses and aliases.

  Do NOT define functions inside the quoted expressions
  below. Instead, define any helper function in modules
  and import those modules here.
  """

  def controller do
    quote do
      use Phoenix.Controller, namespace: RumblWeb
      import Plug.Conn
      import RumblWeb.Gettext
      import RumblWeb.Auth, only: [authenticate_user: 2] # New import
      alias RumblWeb.Router.Helpers, as: Routes
    end
  end

  def view do
    quote do
      use Phoenix.View, root: "lib/rumbl_web/templates",
                        namespace: RumblWeb

      # Import convenience functions from controllers
      import Phoenix.Controller, only: [get_flash: 2, view_module: 1]

      # Use all HTML functionality (forms, tags, etc)
      use Phoenix.HTML

      import RumblWeb.ErrorHelpers
      import RumblWeb.Gettext
      alias RumblWeb.Router.Helpers, as: Routes
    end
  end

  def router do
    quote do
      use Phoenix.Router
      import Plug.Conn
      import Phoenix.Controller
      import RumblWeb.Auth, only: [authenticate_user: 2] # New import
    end
  end

  def channel do
    quote do
      use Phoenix.Channel
      import RumblWeb.Gettext
    end
  end

  @doc """
  When used, dispatch to the appropriate controller/view/etc.
  """
  defmacro __using__(which) when is_atom(which) do
    apply(__MODULE__, which, [])
  end
end

and then RumbWeb itself is used in the controllers.

defmodule RumblWeb.PageController do
  use RumblWeb, :controller

  def index(conn, _params) do
    render conn, "index.html"
  end
end

The above referenced render/2 function is actually imported from the Phoenix.Controller module:

defmodule Phoenix.Controller do

  # ...

  defmacro __using__(opts) do
    quote bind_quoted: [opts: opts] do
      import Phoenix.Controller

      # TODO v2: No longer automatically import dependencies
      import Plug.Conn

      use Phoenix.Controller.Pipeline, opts

      plug :put_new_layout, {Phoenix.Controller.__layout__(__MODULE__, opts), :app}
      plug :put_new_view, Phoenix.Controller.__view__(__MODULE__)
    end
  end

  # ...

  def render(conn, template) when is_binary(template) or is_atom(template) do
    render(conn, template, [])
  end

  # ...

  def render(conn, template, assigns)
      when is_binary(template) and (is_map(assigns) or is_list(assigns)) do
    case Path.extname(template) do
      "." <> format ->
        do_render(conn, template, format, assigns)
      "" ->
        raise "cannot render template #{inspect template} without format. Use an atom if the " <>
              "template format is meant to be set dynamically based on the request format"
    end
  end

  # ...

end
1 Like

If you build elixir from source open will likely work. Otherwise it depends on how it was built and installed.

They are never clearly documented. No one writes docs for their macro definition. Hiding it in the moduledoc is insufficient and bad documentation design.

I think GenServer is a good example of a clearly documented use.

For convenience sake I still think a source link to the __using__ macro definition would be useful - great prose will make understanding the code much easier but in the end I trust the information I get from the code more (+ bonus learning opportunity).