在Elixir宏中使用辅助函数

时间:2016-01-20 05:38:19

标签: macros metaprogramming elixir

继续Elixir文档中的Binding and unquote fragments示例......

我们有一个基于关键字列表定义函数的宏。

defmodule MacroFun do

  defmacro defkv(kv) do
    quote bind_quoted: [kv: kv] do
      Enum.each kv, fn {k, v} ->
        def unquote(k)(), do: unquote(v)
      end
    end
  end

end

defmodule Runner do
  require MacroFun

  kv = [foo: 1, bar: 2]
  MacroFun.defkv(kv)

end

Runner.foo

现在让我们将宏的主体移动到辅助函数。

  defmacro defkv(kv) do
    _defkv(kv)
  end

  defp _defkv(kv) do
    quote bind_quoted: [kv: kv] do
      Enum.each kv, fn {k, v} ->
        def unquote(k)(), do: unquote(v)
      end
    end
  end

太棒了,一切都还行。但是现在如果我们想要在将kv传递给私有帮助函数之前创建另一个修改 defmacro def_modified_kv(kv) do quote bind_quoted: [kv: kv] do modified_kv = Enum.map kv, fn {k, v} -> {k, v + 1} end _defkv(modified_kv) end end 的宏,那该怎么办呢?

_devkv

这不起作用。 Elixir说 defmacro def_modified_kv(kv) do quote bind_quoted: [kv: kv] do modified_kv = Enum.map kv, fn {k, v} -> {k, v + 1} end MacroFun._defkv(modified_kv) end end 没有定义。我们可以使用完全限定的函数名来修复:

MacroFun._defkv

然后Elixir抱怨_devkv是私人的。所以我们将其更改为公开,但仍然无法正常工作,因为帮助方法def_modified_kv会将引用的代码返回到我们引用的宏defmodule MacroFun do defmacro defkv(kv) do _defkv(kv) end defmacro def_modified_kv(kv) do quote bind_quoted: [kv: kv] do modified_kv = Enum.map kv, fn {k, v} -> {k, v + 1} end MacroFun._defkv(modified_kv) |> Code.eval_quoted([], __ENV__) |> elem(0) end end def _defkv(kv) do quote bind_quoted: [kv: kv] do Enum.each kv, fn {k, v} -> def unquote(k)(), do: unquote(v) end end end end

所以我们可以通过eval来帮助修复辅助函数返回的代码(最终代码):

Code.eval_quoted
  1. 为什么我必须更改为通过其完全限定名称调用辅助函数?
  2. 为什么我必须将帮助函数更改为公共(来自私有)?
  3. 除了致电 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); try { ViewConfiguration config = ViewConfiguration.get(getActivity()); Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey"); if (menuKeyField != null) { menuKeyField.setAccessible(true); menuKeyField.setBoolean(config, false); } } catch (Exception e) { e.printStackTrace(); } setHasOptionsMenu(true); }
  4. 之外,还有更好的方法吗?

    我觉得我做错了什么。

    感谢您的帮助。

2 个答案:

答案 0 :(得分:0)

  1. 您需要导入模块。
  2. 导入模块时,只能导入公共方法。

  3. 宏是一个编译时功能,它们在模块中实际写入代码(或AST)。因此,当宏扩展时,宏的上下文就是你调用/使用它的地方。因此,如果您使用宏(未定义)SomeOtherModule这是宏扩展的上下文。在SomeOtherModule中,您需要import MacroFun在本地调用其功能。

  4. 以下代码适用于Elixir> = 1.2.0

    E.g。

    defmodule MacroFun do
    
      defmacro defkv(kv) do
        _defkv(kv)
      end
    
      defp _defkv(kv) do
        quote bind_quoted: [kv: kv] do
          Enum.each kv, fn {k, v} ->
            def unquote(k)(), do: unquote(v)
          end
        end
      end
    
      defmacro def_modified_kv(kv) do
        quote bind_quoted: [kv: kv] do
          modified_kv = Enum.map kv, fn {k, v} -> {k, v + 1} end
          defkv(modified_kv)
        end
      end
    end
    
    defmodule Runner do
      import MacroFun
    
      kv = [foo: 1, bar: 2]
      def_modified_kv(kv)
    
    end
    

    希望这能回答你的问题!祝你好运。

    更新了几次,因为宏有点令人困惑!

答案 1 :(得分:0)

1)通过使用require,您已告诉编译器编译并提供MacroFun公共宏和函数。请注意,无论如何都可以使用这些功能:

  

请注意,通常在使用前不需要模块,唯一的   例外是如果你想使用模块中的宏。

2)他们可以通过他们的FQN

获得

3)这是我的看法:

defmodule MicroFun do
  defmacro defkv(kv) do
    _defkv(kv)
  end

  defp _defkv(kv) do
    quote bind_quoted: [kv: kv] do
      Enum.each kv, fn {k,v} ->
        def unquote(k)(), do: unquote(v)
      end
    end
  end

  defmacro modifier(kv) do
    quote bind_quoted: [kv: kv] do
      Enum.map kv, fn {k,v} -> {k, v+1} end
    end
  end
end

defmodule Runner do
  require MicroFun

  [foo: 1, bar: 2]
  |> MicroFun.modifier
  |> MicroFun.defkv
end

我们的想法是,只需管道它们就不需要嵌套宏/函数。