扩展宏

时间:2016-10-29 20:24:36

标签: macros elixir

我想知道是否有可能在宏观上扩大范围。

目前,当我尝试使用此代码时:

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"。是否有可能使这个想法有效?

1 个答案:

答案 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