我对跟随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)
是什么?
答案 0 :(得分:2)
quote
和unquote
做的是创建一个抽象语法树(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中,函数名称看起来像变量,与可选的括号相结合可能使代码更难以快速阅读和理解。