它是Elixir中的common to have constants as Module Attributes。我试图将模块属性作为参数传递给来自不同库(通常定义一个新模块)的不同宏:
defmodule X do
@data_types [:x, :y, :z]
@another_constant "some-constant-value"
defenum DataType, :type, @data_types
end
这是passing module attributes as an argument to a Macro
的另一个示例但几乎总是会出现相同的错误:
** (Protocol.UndefinedError) protocol Enumerable not implemented for {:@, [line: 26], [{:types, [line: 26], nil}]}
所以我通常最终会重复这些值:
defmodule X do
@data_types [:x, :y, :z]
@another_constant "some-constant-value"
defenum DataType, :type, [:x, :y, :z]
end
我知道大多数时候重复一次通常没什么大不了的,但是我真的很想知道如何将模块属性的值传递给宏。
这在定义新模块(例如Amnesia
和EctoEnum
)的宏中尤为明显。
到目前为止,我已经尝试了很多方法,包括:
Macro
模块扩展值Code
模块评估值Module.get_attribute/2
但是没有任何效果。我感觉需要以一种可以读取宏的方式编写宏。如果是这样,应该如何编写宏才能使其工作?
答案 0 :(得分:1)
不幸的是,通过将任意带引号的表达式传递到外部库来解决此问题的唯一方法是提供在库中解决问题的请求请求。
考虑以下示例
defmodule Macros do
defmacro good(param) do
IO.inspect(param, label: " Passed")
expanded = Macro.expand(param, __CALLER__)
IO.inspect(expanded, label: " Expanded")
end
defmacro bad(param) do
IO.inspect(param, label: " Not Expanded")
end
end
defmodule Test do
import Macros
@data_types [:x, :y, :z]
def test do
good(@data_types)
bad(@data_types)
end
end
Test
的声明打印:
Passed: {:@, [line: 28], [{:data_types, [line: 28], nil}]}
Expanded: [:x, :y, :z]
Not Expanded: {:@, [line: 29], [{:data_types, [line: 29], nil}]}
如果第三方库未在参数上调用Macro.expand/2
,则引用的表达式将不会展开。以下是文档摘录:
以下内容已展开:
•宏(本地或远程)
•别名被扩展(如果可能)并返回原子
•编译环境宏(__CALLER__/0
,__DIR__/0
,__ENV__/0
和__MODULE__/0
)
•模块属性读取器(@foo
)
也就是说,要具有接受模块属性或信号之类的宏调用的能力,第三方库宏必须对参数调用Macro.expand
。您无法通过客户端代码解决此问题。
答案 1 :(得分:0)
但是几乎总是会出错...
在此示例中,我可以访问宏内的模块属性:
defmodule My do
@data_types [:x, :y, :z]
defmacro go() do
quote do
def types() do
unquote(@data_types)
end
end
end
end
defmodule Test do
require My
My.go()
def start do
types()
end
end
在iex中:
~/elixir_programs$ iex my.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Test.start()
[:x, :y, :z]
iex(2)>
回复评论:
下面是一个将模块属性作为参数传递给宏函数调用的示例:
defmodule My do
defmacro go(arg) do
quote do
def show do
Enum.each(unquote(arg), fn x -> IO.inspect x end)
end
end
end
end
defmodule Test do
@data_types [:x, :y, :z]
require My
My.go(@data_types)
def start do
show()
end
end
在iex中:
~/elixir_programs$ iex my.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Test.start()
:x
:y
:z
:ok
iex(2)>
如果相反,请尝试以下操作:
defmodule My do
defmacro go(arg) do
Enum.each(arg, fn x -> IO.inspect x end)
end
end
defmodule Test do
require My
@data_types [:x, :y, :z]
My.go(@data_types)
def start do
show()
end
end
然后在iex中会出现错误:
~/elixir_programs$ iex my.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
** (Protocol.UndefinedError) protocol Enumerable not implemented for {:@, [line: 11], [{:data_types, [line: 11], nil}]}
(elixir) lib/enum.ex:1: Enumerable.impl_for!/1
(elixir) lib/enum.ex:141: Enumerable.reduce/3
(elixir) lib/enum.ex:1919: Enum.each/2
expanding macro: My.go/1
my.exs:11: Test (module)
发生该错误是因为您试图枚举ast:
{:@, [line: 11], [{:data_types, [line: 11], nil}]}
这不是一个列表(或任何其他可枚举的-显然是一个元组!)。请记住,宏的参数是ast的。
此方法起作用的原因:
defmacro go(arg) do
quote do
def show do
Enum.each(unquote(arg), fn x -> IO.inspect x end)
end
end
end
是因为quote()
创建了一个ast,而unquote(arg)
将其他ast注入了ast的中间。宏调用My.go(@data_types)
在编译时执行,并且elixir用My.go()
返回的ast替换宏调用,show()
是名为{{1}}的函数定义。