什么时候应该在 .exs 文件中使用 defmodule

When we should use defmodule in .exs file

我尝试创建一个没有 config.exs 的混合项目,所以我创建了文件。

我从 phoenix 项目中推荐了 config.exs

我看到 config.exs 没有模块定义。我尝试在 .ex 文件中声明一个独立函数,它按预期引发了错误

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

然后我假设 .exs 可以不写 defmodule 然后我看到 mix.exs 它有模块定义。为什么会这样?

我的问题是为什么在没有模块定义的情况下保留 config.exs 而在有定义的情况下保留 mix.exs

我们什么时候应该在 .exs 中使用 defmodule,什么时候不应该?

如果您查看 mix.exs 文件,您会注意到:

use Mix.Project

现在这个 file 包含:

  @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 是在 elixir/kernel.ex 中定义的宏,即使它的形式很奇怪,它是由 __using__mix.exs 调用的。由于您不能在模块外调用宏,因此您需要在 mix.exs 文件中包含一个模块。

为了更清楚地说明这一点,让我们尝试删除 mix.exs 中的模块和 运行 项目:

* (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)

所以你的问题的答案是钩子 @after_compile 不能在没有模块的情况下调用,因为钩子本身就是宏。该钩子很可能用于在所有文件编译完成后自动加载项目。

PS: push/3 函数调用了一个有趣的模块函数:

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

如果你查看 ProjectStack module you can observe that it is a state machine based on Agent 的来源。所以基本上所有的mix项目都压入栈中,可以检查是否重名。