我想知道是否有可能在宏观上扩大范围。
目前,当我尝试使用此代码时:
defmodule Main do
defmacro is_atom_literal(char) do
quote do:
Enum.any?(unquote([?a..?z, ?A..?Z, ?0..?9, [?_, ?-]]), &(unquote(char) in &1))
end
def test do
c = 'b'
case c do
c when is_atom_literal(c) ->
:ok
end
end
end
Main.test
我收到错误"** (CompileError) test.ex: invalid quoted expression: 97..122"
。是否有可能使这个想法有效?
答案 0 :(得分:1)
修复"无效的引用表达式"您可以像这样使用Macro.escape/1
:
Enum.any?(unquote(Macro.escape([?a..?z, ?A..?Z, ?0..?9, [?_, ?-]])), &(unquote(char) in &1))
但是这会引发另一个错误:
** (CompileError) a.exs:10: invalid expression in guard
expanding macro: Main.is_atom_literal/1
a.exs:10: Main.test/0
这是因为您试图在警卫中呼叫Enum.any?/2
,这是不允许的。
幸运的是,有一种解决方法:只需加入or
的所有表达式。这可以使用Enum.reduce/3
:
defmacro is_atom_literal(char) do
list = [?a..?z, ?A..?Z, ?0..?9, [?_, ?-]]
Enum.reduce list, quote(do: false), fn enum, acc ->
quote do: unquote(acc) or unquote(char) in unquote(Macro.escape(enum))
end
end
此代码的作用是将is_atom_literal(c)
转换为:
false or c in %{__struct__: Range, first: 97, last: 122} or c in %{__struct__: Range, first: 65, last: 90} or c in %{__struct__: Range, first: 48, last: 57} or c in '_-'
这是一个有效的保护表达式,因为Elixir后来将范围和列表in
变为更简单的语句(类似c >= 97 and c <= 122 or c >= 65 and c <= 90 or ...
)。
代码仍然失败,因为您的输入是'b'
,而宏需要一个字符。将'b'
更改为?b
有效:
defmodule Main do
defmacro is_atom_literal(char) do
list = [?a..?z, ?A..?Z, ?0..?9, [?_, ?-]]
Enum.reduce list, quote(do: false), fn enum, acc ->
quote do: unquote(acc) or unquote(char) in unquote(Macro.escape(enum))
end
end
def test do
c = ?b
case c do
c when is_atom_literal(c) ->
:ok
end
end
end
IO.inspect Main.test
输出:
:ok