【问题标题】:Unable to understand import via __using__ macro Elixir/Phoenix无法理解通过 __using__ 宏 Elixir/Phoenix 的导入
【发布时间】:2015-12-28 00:06:48
【问题描述】:

我正在使用 Phoenix 框架并尝试创建用于身份验证的插件,但遇到错误。下面是这个示例代码。

defmodule ChhutiServer.GoogleAuthController do
  use ChhutiServer.Web, :controller
  use ChhutiServer.Plugs.GoogleAuth
end

# inside lib/plugs

defmodule ChhutiServer.Plugs.GoogleAuth do
  import Plug.Conn

  defmodule ChhutiServer.Behaviour.GoogleAuth do
    @callback callback(Plug.Conn.t, map) :: any
  end

  defmacro __using__(_) do
    quote do
      plug ChhutiServer.Plugs.GoogleAuth
    end
  end
end

上面的代码返回下面的错误。

== Compilation error on file web/controllers/google_auth_controller.ex ==
** (UndefinedFunctionError) undefined function: ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Plugs.GoogleAuth.init/1 (module ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Plugs.GoogleAuth is not available)
    ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Plugs.GoogleAuth.init([])
    (plug) lib/plug/builder.ex:198: Plug.Builder.init_module_plug/3
    (plug) lib/plug/builder.ex:186: anonymous fn/4 in Plug.Builder.compile/3
    (elixir) lib/enum.ex:1387: Enum."-reduce/3-lists^foldl/2-0-"/3
    (plug) lib/plug/builder.ex:186: Plug.Builder.compile/3
    (phoenix) expanding macro: Phoenix.Controller.Pipeline.__before_compile__/1
    web/controllers/google_auth_controller.ex:1: ChhutiServer.GoogleAuthController (module)
    (elixir) lib/kernel/parallel_compiler.ex:100: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/8

我了解错误是由于 Elixir 寻找 ChhutiServe.Plugs.GoogleAuth.ChhuttiServer.Plugs.GoogleAuth 而不是 ChhuttiServe.Plugs.GoogleAuth。而且我还可以通过在模块前面加上 Elixir 命名空间来解决这个问题。即通过使用下面的代码。

defmodule ChhutiServer.Plugs.GoogleAuth do
  import Plug.Conn

  defmodule ChhutiServer.Behaviour.GoogleAuth do
    @callback callback(Plug.Conn.t, map) :: any
  end

  defmacro __using__(_) do
    quote do
      plug Elixir.ChhutiServer.Plugs.GoogleAuth
    end
  end
end

但我无法理解它为什么会这样。谁能帮帮我。

更新

添加所有必需的代码。

【问题讨论】:

  • MyApp.Web的内容是什么?上面的代码应该可以正常运行。
  • MyApp.Web 是 phoenix 生成的默认模块(web.ex 文件)。它不包含我添加的任何行。我应该在这里发布整个代码吗?文件有很多行代码,所以我避免在这里粘贴。但我想我错过了粘贴有问题的部分。我将在这里发布 github repo 的链接,而不是在这里粘贴所有内容

标签: elixir phoenix-framework


【解决方案1】:

问题是因为我在ChhutiServer.Plugs.GoogleAuth 中声明了模块ChhutiServer.Behaviour.GoogleAuth。但是报告的错误非常奇怪和误导。将ChhutiServer.Behaviour.GoogleAuth 移到ChhutiServer.Behaviour.GoogleAuth 之外可以解决问题。我注意到的另一件奇怪的事情是将ChhutiServer.Behaviour.GoogleAuth 移动到__using__ 宏下方也可以消除错误。由于错误报告看起来很奇怪,我提出了一个问题。下面是问题

https://github.com/elixir-lang/elixir/issues/4120

更新

Jose Valim 帮助我理解这不是问题,这是由于我们定义嵌套模块时创建的别名。我将解释为什么我们会出现上述错误,以便其他面临类似问题的人可以理解这个概念。

每当我们在 elixir 中创建嵌套模块时,我们都会为嵌套模块创建别名。下面是例子。

defmodule A do
  defmodule B do
  end
end

以上代码等价于

defmodule A.B do
end

defmodule A do
  alias A.B, as: B
end

因此嵌套模块创建了别名,以便可以在没有完全限定名称的情况下在父模块内访问它,即我们可以在 A 中直接使用 B 而不是 A.B 来访问 B。

现在让我们来看看我的问题并检查发生了什么并了解生成的错误。

defmodule ChhutiServer.Plugs.GoogleAuth do
  import Plug.Conn


  defmodule ChhutiServer.Behaviour.GoogleAuth do
    @callback callback(Plug.Conn.t, map) :: any
  end


  defmacro __using__(_) do
    quote do
      plug ChhutiServer.Plugs.GoogleAuth
    end
  end
end

在上面的代码中,模块ChhutiServer.Behaviour.GoogleAuth 嵌套在ChhutiServer.Plugs.GoogleAuth 中。所以ChhutiServer.Behaviour.GoogleAuth 的完全限定名称是ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Behaviour.GoogleAuth。正如我们所见,嵌套模块创建了别名,上面的代码与

  defmodule ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Behaviour.GoogleAuth do
    @callback callback(Plug.Conn.t, map) :: any
  end

  defmodule ChhutiServer.Plugs.GoogleAuth do
    import Plug.Conn
    alias ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Behaviour.GoogleAuth, as: ChhutiServer.Behaviour.GoogleAuth   

    defmacro __using__(_) do
     quote do
       plug ChhutiServer.Plugs.GoogleAuth
     end
    end
  end

由于这个别名现在ChhutiServer 指向ChhutiServer.Plugs.GoogleAuth.ChhutiServer,所以我们可以在模块内部使用ChhutiServer.Behaviour.GoogleAuth ChhutiServer.Plugs.GoogleAuth 而不是使用全名ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Behaviour.GoogleAuth。那么当我们使用 plug ChhutiServer.Plugs.GoogleAuth__using__ 宏内,它现在扩展为 plug ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Plugs.GoogleAuth ChhutiServer 指向 ChhutiServer.Plugs.GoogleAuth.ChhutiServer。并且出现了我们的错误,因为 elixir 无法找到该模块 ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Plugs.GoogleAuth

我还指出,当我们在 __using__ 宏下面定义模块 ChhutiServer.Behaviour.GoogleAuth 时,上面的代码运行时没有任何错误。 这是因为别名在 elixir 中是词法范围的。因此,如果我们将ChhutiServer.Behaviour.GoogleAuth 放在__using__ 宏下面,则别名将在宏下面定义。 所以plug ChhutiServer.Plugs.GoogleAuth 没有扩展到任何东西,它保持原样。而elixir 能够找到ChhutiServer.Plugs.GoogleAuth。小例子来解释上面的事情。

defmodule A.B 做 定义 b 做 结尾 结束

defmodule A 做 定义做 # 将返回错误“模块 B 不可用”,因为 A.B 还没有别名。 # 如果我们想调用b,我们必须写成A.B.b 乙 结尾 别名 A.B,如:B 结束

如果我们在使用B 之前使用别名,它将起作用。

defmodule A 做 别名 A.B,如:B
定义做 乙 结尾 结束

希望这有助于其他人理解嵌套模块和别名。感谢 Jose Valim 的解释

参考资料:

http://elixir-lang.org/getting-started/alias-require-and-import.html

https://github.com/elixir-lang/elixir/issues/4120

【讨论】:

  • 感谢您的报告。我试图解决你的问题,但没有运气。但是现在,在您的帖子和@josevalim 回复您的问题之后,我对别名和嵌套模块的工作方式有了更好的理解。您还可以在问题报告中编辑您的帖子并在此处提供解释。
  • @DmitryBiletskyy 当然,一旦我明确了几点,我会更新帖子。我仍然有些不清楚,并在同一问题上发布了其他问题。将等到我完全清楚。您是否还可以检查发布在该问题上的问题。你可能有我正在寻找的答案。
  • @DmitryBiletskyy 在答案中添加了一些细节以详细解释问题
猜你喜欢
  • 2017-04-05
  • 2017-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多