Search code examples
elixirelixir-mix

When we should use defmodule in .exs file


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?


Solution

  • 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.