1

正如代码https://github.com/elixir-plug/plug/blob/v1.5.0/lib/plug/builder.ex#L183所示,插件定义将在宏扩展阶段编译为 AST。但为什么?为什么不只保留插件定义并使用Enum.reduce_while或递归来一一调用插件?

4

1 回答 1

1

我能想到的两个原因:

  1. 表现。考虑这两个片段做同样的事情,但一个使用编译的函数调用,另一个使用Enum.reduceand apply

    defmodule A do
      def add1(x), do: x + 1
      def sub1(x), do: x - 1
    
      def compiled(x) do
        x
        |> add1()
        |> sub1()
        |> add1()
        |> sub1()
        |> add1()
        |> sub1()
        |> add1()
        |> sub1()
      end
    
      @pipeline [
        {A, :add1},
        {A, :sub1},
        {A, :add1},
        {A, :sub1},
        {A, :add1},
        {A, :sub1},
        {A, :add1},
        {A, :sub1}
      ]
      def runtime(x) do
        Enum.reduce(@pipeline, x, fn {module, function}, acc ->
          apply(module, function, [acc])
        end)
      end
    end
    

    一个简单的基准测试表明运行时实现慢了 5 倍。

    IO.inspect(
      :timer.tc(fn ->
        for _ <- 1..1_000_000, do: A.compiled(123)
        :ok
      end)
      |> elem(0)
    )
    
    IO.inspect(
      :timer.tc(fn ->
        for _ <- 1..1_000_000, do: A.runtime(123)
        :ok
      end)
      |> elem(0)
    )
    

    输出:

    82800
    433198
    
  2. 在编译时捕获错误。如果将模块传递给plug未实现的模块call/2,则会在编译时收到错误,而不是在运行时执行所有操作时通常会遇到的运行时错误。

于 2018-04-18T16:03:16.427 回答