无法理解使用宏Elixir/Phoenix通过导入

无法理解使用宏Elixir/Phoenix通过导入,elixir,phoenix-framework,Elixir,Phoenix Framework,我正在使用Phoenix框架并尝试创建一个用于身份验证的插件,但遇到了一个错误。下面是这个示例代码 defmodule ChhutiServer.GoogleAuthController do use ChhutiServer.Web, :controller use ChhutiServer.Plugs.GoogleAuth end # inside lib/plugs defmodule ChhutiServer.Plugs.GoogleAuth do import Plug

我正在使用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名称空间预先设置模块来解决这个问题。ie使用下面的代码

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
但我不明白为什么它会这样。谁能帮帮我吗

更新


添加所有必需的代码

问题是因为我在
ChhutiServer.Plugs.GoogleAuth
内部声明了模块
ChhutiServer.behavior.GoogleAuth
。但报告的错误是非常奇怪和误导的。将
ChhutiServer.behavior.GoogleAuth
移动到
ChhutiServer.behavior.GoogleAuth
外部解决了这个问题。我注意到的另一件奇怪的事情是,在
\u下移动
ChhutiServer.behavior.GoogleAuth
,使用
宏也会删除错误。由于错误报告看起来很奇怪,我提出了一个问题。以下是问题

更新

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
因此嵌套模块创建别名,以便可以在父模块内部访问别名,而无需完全限定名,即我们可以使用
B
而不是
A.B
直接在A内部访问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.behavior.GoogleAuth
嵌套在
ChhutiServer.Plugs.GoogleAuth
中。所以
ChhutiServer.behavior.GoogleAuth
的完全限定名是
ChhutiServer.Plugs.GoogleAuth.ChhutiServer.behavior.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.behavior.GoogleAuth
ChhutiServer.Plugs.GoogleAuth
而不是使用全名
ChhutiServer.Plugs.GoogleAuth.ChhutiServer.behavior.GoogleAuth
。那么当我们使用
plug ChhutiServer.Plugs.GoogleAuth
内部
\uu使用
宏将其扩展为
plug ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Plugs.GoogleAuth
ChhutiServer
指向
ChhutiServer.Plugs.GoogleAuth.ChhutiServer
。我们的错误来了,因为elixir无法找到模块
ChhutiServer.Plugs.GoogleAuth.ChhutiServer.Plugs.GoogleAuth

我还质疑地指出,当我们在
\u下面使用
宏定义module
ChhutiServer.behavior.GoogleAuth
时,上面的代码运行时没有任何错误。 这是因为alias在elixir中是词汇范围。因此,如果我们使用宏将
ChhutiServer.behavior.GoogleAuth
置于
\u下方,则别名将在宏下方定义。
因此,
plug ChhutiServer.Plugs.GoogleAuth
没有扩展为任何内容,而是保持原样。elixir能够找到ChhutiServer.Plugs.GoogleAuth
。用一个小例子来解释上面的事情

模块A.B.do def b do 结束 结束

defado模块 定义一个do #将返回错误“模块B不可用”,因为A.B尚未别名。 #如果我们想调用
b
我们必须将其写成
A.b.b
B.B 结束 别名A.B,as:B 结束

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

defado模块 别名A.B,as:B
定义一个do B.B 结束 结束

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

参考资料:


MyApp.Web的内容是什么?上面的代码应该可以正常运行。MyApp.Web是phoenix生成的默认模块(Web.ex文件)。它不包含我添加的任何行。我应该在这里发布完整的代码吗。文件有很多行代码,所以我避免将其粘贴到这里。但我想我错过了粘贴小车部件。我将在这里发布github repo的链接,而不是将所有内容都粘贴到这里谢谢你的报告。我试图解决你的问题,但运气不好。但是现在,在你的帖子和@josevalim回复你的问题之后,我对别名和嵌套模块的工作原理有了更好的理解。您也可以编辑您的帖子,并在这里从问题报告中进行解释。@DmitryByletsky我确信,一旦我弄清楚了几点,我会更新帖子。我仍然有点不清楚,并已就同一问题发布了其他问题。我会等到我完全明白。你能不能也查一下这期杂志上的问题。您可能已经得到了我想要的答案。@Dmitrybiletsky在答案中添加了一些细节,以详细解释这个问题