【问题标题】:Expand range in macro在宏中扩展范围
【发布时间】:2016-10-30 04:47:04
【问题描述】:

我想知道是否可以扩展宏的范围。

目前当我尝试这段代码时:

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"。这个想法有可能实现吗?

【问题讨论】:

    标签: macros elixir


    【解决方案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
    

    【讨论】:

    • 很好,很好用,谢谢。后续问题:您能想出一种更好的方法来检查单个字符是否是该列表中的任何字符吗?现在有点乱,不得不减少它。
    • 如果只需要检查这 4 个范围,我可能会在宏中编写扩展形式:quote do: unquote(c) >= 65 and unquote(c) <= 90 or ...
    • 其实你可以做得更好:quote do: unquote(c) in ?a..?z or unquote(c) in ?A..?Z or ....
    猜你喜欢
    • 1970-01-01
    • 2023-04-04
    • 2016-08-23
    • 1970-01-01
    • 2018-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多