无法理解通过 __using__ 宏 Elixir/Phoenix 的导入
Unable to understand import via __using__ macro Elixir/Phoenix
我正在使用 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
以上代码 returns 错误以下。
== 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
我知道这个错误是由于长生不老药寻找 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
但我无法理解为什么它会这样。谁能帮帮我。
更新
添加所有必需的代码。
问题是因为我在 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 做
绝对要做
# 将 return 错误 "module B is not available" 因为 A.B 还没有别名。
# 如果我们想调用 b
我们必须把它写成 A.B.b
B.b
结尾
别名 A.B,如:B
结束
如果我们在使用 B
之前使用别名,它将起作用。
defmodule A 做
别名 A.B,如:B
绝对要做
B.b
结尾
结束
希望这有助于其他人理解嵌套模块和别名。感谢 Jose Valim 的解释
参考文献:
http://elixir-lang.org/getting-started/alias-require-and-import.html
我正在使用 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
以上代码 returns 错误以下。
== 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
我知道这个错误是由于长生不老药寻找 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
但我无法理解为什么它会这样。谁能帮帮我。
更新
添加所有必需的代码。
问题是因为我在 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 做
绝对要做
# 将 return 错误 "module B is not available" 因为 A.B 还没有别名。
# 如果我们想调用 b
我们必须把它写成 A.B.b
B.b
结尾
别名 A.B,如:B
结束
如果我们在使用 B
之前使用别名,它将起作用。
defmodule A 做
别名 A.B,如:B
绝对要做
B.b
结尾
结束
希望这有助于其他人理解嵌套模块和别名。感谢 Jose Valim 的解释
参考文献:
http://elixir-lang.org/getting-started/alias-require-and-import.html