繁体   English   中英

在Elixir宏中使用辅助函数

[英]Using helper functions in Elixir macros

继续Elixir文档中的Binding和unquote片段示例...

我们有一个宏,用于根据关键字列表定义功能。

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

那不行 Elixir说_devkv没有定义。 我们可以使用完全限定的函数名称来修复:

  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

但是随后Elixir抱怨MacroFun._defkv是私有的。 因此,我们将其更改为public,但是它仍然无法正常工作,因为辅助方法_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
  1. 为什么必须更改为使用其完全限定的名称来调用辅助函数?
  2. 为什么我必须将助手功能更改为公开(从私有)?
  3. 除了调用Code.eval_quoted之外,还有更好的方法吗?

我觉得我做错了什么。

谢谢您的帮助。

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

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

下面的代码在Elixir> = 1.2.0中有效

例如

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)通过使用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

这个想法是,当您可以通过管道传递宏/函数时,就不需要嵌套它们。

暂无
暂无

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

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