时间:2016-04-14 14:19:37

标签: elixir

我对跟随marco有疑问。请考虑以下代码段:

defmodule Assertion do

  defmacro __using__(_options) do
    quote do
      import unquote(__MODULE__)
      Module.register_attribute __MODULE__, :tests, accumulate: true
      @before_compile unquote(__MODULE__)
    end
  end

  defmacro __before_compile__(_env) do
    IO.puts "before compile"
    quote do
      def run, do: Assertion.Test.run(@tests, __MODULE__)     
    end
  end

  defmacro test(description, do: test_block) do
    test_func = String.to_atom(description)
    quote do
      @tests {unquote(test_func), unquote(description)}
      def unquote(test_func)(), do: unquote(test_block)
    end
  end

  defmacro assert({operator, _, [lhs, rhs]}) do
        IO.puts "assert"
    quote bind_quoted: [operator: operator, lhs: lhs, rhs: rhs] do
      Assertion.Test.assert(operator, lhs, rhs)
    end
  end
end

defmodule Assertion.Test do
  def run(tests, module) do                              
    Enum.each tests, fn {test_func, description} ->
      case apply(module, test_func, []) do
        :ok             -> IO.write "."
        {:fail, reason} -> IO.puts """

          ===============================================
          FAILURE: #{description}
          ===============================================
          #{reason}
          """
      end
    end
  end                                                      

  def assert(:==, lhs, rhs) when lhs == rhs do
    :ok
  end
  def assert(:==, lhs, rhs) do
    {:fail, """
      Expected:       #{lhs}
      to be equal to: #{rhs}
      """
    }
  end

  def assert(:>, lhs, rhs) when lhs > rhs do
    :ok
  end
  def assert(:>, lhs, rhs) do
    {:fail, """
      Expected:           #{lhs}
      to be greater than: #{rhs}
      """
    }
  end
end

以下模块使用宏:

defmodule MathTest do
  use Assertion
  test "integers can be added and subtracted" do
    assert 2 + 3 == 5
    assert 5 - 5 == 10
  end
end

在线查看测试宏

def unquote(test_func)(), do: unquote(test_block)

我在这里向调用者模块注入一个名为integers can be added and subtracted的函数,然后将其转换为原子。

如何为具有空格的函数命名? ()之后的def unquote(test_func)是什么?

1 个答案:

答案 0 :(得分:2)

quoteunquote做的是创建一个抽象语法树(AST)。让我们来看看并进行比较

正常功能定义

iex(1)> quote do def foo(), do: :ok end
{:def, [context: Elixir, import: Kernel],
 [{:foo, [context: Elixir], []}, [do: :ok]]}

我们可以看到函数的名称作为{:foo, [context: Elixir], []}存储在AST中,也就是说,名称存储为原子

使用unquote

的函数定义
iex(2)> foovar=:'foo var'              
:"foo var"
iex(3)> quote do def unquote(foovar)(), do: :ok end
{:def, [context: Elixir, import: Kernel],
 [{:"foo var", [context: Elixir], []}, [do: :ok]]}

这里名称存储在AST中,元组的名称部分为先前定义的变量:"foo var"

您可以使用内置函数apply

调用这些函数

()是可选的。有些人,包括我自己,更喜欢在所有函数定义和函数调用中使用它们以保持一致性。

注意:

我更喜欢Erlang语法的一个方面是函数名和原子看起来一样,它们的排序是相同的。那就是你可以说Module.foo只是apply(Module, :foo, [])的糖。在elixir中,函数名称看起来像变量,与可选的括号相结合可能使代码更难以快速阅读和理解。