简体   繁体   English

在Elixir宏中使用辅助函数

[英]Using helper functions in Elixir macros

Continuing the Binding and unquote fragments example from the Elixir documentation... 继续Elixir文档中的Binding和unquote片段示例...

We have a macro that defines functions based on a Keyword list. 我们有一个宏,用于根据关键字列表定义功能。

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

Now let's move the body of the macro to a helper function. 现在,让我们将宏的主体移至辅助函数。

  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

Great, everything still works. 太好了,一切仍然有效。 But now what if we want to make another macro that modifies kv before passing it to the private helper function: 但是现在,如果我们要制作另一个修改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

That doesn't work. 那不行 Elixir says _devkv is not defined. Elixir说_devkv没有定义。 We can fix by using a fully qualified function name: 我们可以使用完全限定的函数名称来修复:

  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

But then Elixir complains that MacroFun._defkv is private. 但是随后Elixir抱怨MacroFun._defkv是私有的。 So we change it to public, but it still doesn't work because the helper method _devkv returns quoted code to our macro def_modified_kv which itself is quoted! 因此,我们将其更改为public,但是它仍然无法正常工作,因为辅助方法_devkv将引用的代码返回到宏def_modified_kv ,该宏本身被引用了!

So we can fix that by eval'ing the code returned by the helper function (final code): 因此,我们可以通过评估辅助函数返回的代码(最终代码)来解决此问题:

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
  1. Why did I have to change to calling the helper function by its fully qualified name? 为什么必须更改为使用其完全限定的名称来调用辅助函数?
  2. Why did I have to change the helper function to be public (from private)? 为什么我必须将助手功能更改为公开(从私有)?
  3. Is there a better way to do this besides calling Code.eval_quoted ? 除了调用Code.eval_quoted之外,还有更好的方法吗?

I feel like I'm doing something wrong. 我觉得我做错了什么。

Thanks for the help. 谢谢您的帮助。

  1. You need to import the module. 您需要导入模块。
  2. When you import a module, you can only import public methods. 导入模块时,只能导入公共方法。

  3. Macros are a compile time feature, they write actually write code (or AST) in the module. 宏是编译时的功能,它们在模块中实际写入代码(或AST)。 So, the context of the macro when it's expanded, is where you're calling/using it. 因此,宏展开时的上下文就是您调用/使用它的地方。 Thus, if you use a macro (not defined in) SomeOtherModule that's the context of macro expansion. 因此,如果使用宏(未定义) SomeOtherModule ,则它是宏扩展的上下文。 In SomeOtherModule you would need to import MacroFun to call its functions locally. SomeOtherModule您需要import MacroFun才能在本地调用其功能。

The code below works in Elixir >= 1.2.0 下面的代码在Elixir> = 1.2.0中有效

Eg 例如

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

Hope this answers your questions! 希望这能回答您的问题! Good luck. 祝好运。

Updated a couple times because macros are a little confusing! 更新了两次,因为宏有些混乱!

1) By using require , you've told the compiler to compile and make available MacroFun s public macros and functions. 1)通过使用require ,您已经告诉编译器编译并提供MacroFun公共宏和函数。 Note that the functions would be available anyway though: 请注意,这些功能仍然可以使用:

Notice that usually modules should not be required before usage, the only exception is if you want to use the macros from a module. 请注意,通常在使用前不需要模块,唯一的例外是如果要使用模块中的宏。

2) They are available through their FQN 2)可通过FQN获得

3) Here's my take on it: 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

The idea is that you don't need to nest macros/functions when you can just pipe them. 这个想法是,当您可以通过管道传递宏/函数时,就不需要嵌套它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM