3

I tried creating a mix project it did not have the config.exs so I created the file.

I referred config.exs from a phoenix project.

I see the config.exs does not have module definition. I tried declaring a standalone function in a .ex file it raised an error as expected

** (ArgumentError) cannot invoke def/2 outside module

Then I presumed that .exs can be written without defmodule then I saw the mix.exs it has module definition. Why it is so?

My question why it is preffred to keep config.exs without module definition but mix.exs with the definition?

When we should use defmodule in .exs and when not?

Yugandhar Chaudhari
  • 3,831
  • 3
  • 24
  • 40
  • 2
    `.exs` is just a script and has nothing to do with whether or not you use `defmodule` inside it. My guess (I don't have any evidence to support it) is the main difference is API simplicity. With a config file, you're just saying "declare all this config". With a mix project file, you're modeling a mix project, which has certain things that need to be known about it. – Brett Beatty Nov 04 '19 at 19:48
  • @BrettBeatty That makes sense – Yugandhar Chaudhari Nov 05 '19 at 05:52

1 Answers1

3

If you take a look at mix.exs file you can notice the:

use Mix.Project

Now this file contains:

  @doc false
  defmacro __using__(_) do
    quote do
      @after_compile Mix.Project
    end
  end

  # Invoked after each Mix.Project is compiled.
  @doc false
  def __after_compile__(env, _binary) do
    push(env.module, env.file)
  end

@after_compile is a macro defined in elixir/kernel.ex even if it is in a strange form and it is invoked by __using__ from mix.exs. Since you cannot invoke macros outside of modules you need to have a module in your mix.exs file.

To illustrate this more clearly, let's try to delete the module in mix.exs and run the project:

* (ArgumentError) cannot invoke @/1 outside module
    (elixir) lib/kernel.ex:5230: Kernel.assert_module_scope/3
    (elixir) expanding macro: Kernel.@/1
    mix.exs:2: (file)
    (mix) expanding macro: Mix.Project.__using__/1
    mix.exs:2: (file)
    (elixir) expanding macro: Kernel.use/1
    mix.exs:2: (file)

So the answer to your question is that the hook @after_compile cannot be called without a module since hooks by themselves are macros. The hook most probably is used to load the project automatically after all the files were compiled.

PS: push/3 function calls an interesting module function:

Mix.ProjectStack.push(atom, config, file)

If you look at the source of ProjectStack module you can observe that it is a state machine based on Agent. So basically all mix projects are pushed into the stack and they can be checked whether there are duplicates in names.

Daniel
  • 2,320
  • 1
  • 14
  • 27