首页 > 解决方案 > 如何编写一个 DSL 来创建一个带参数的函数?

问题描述

我想定义一个宏,以便我可以将一个do块传递给它,并让宏创建一个调用该块的函数,并带有一个参数。我遇到了鸡和蛋的问题,因为下面的代码抱怨name没有定义。

defmodule MyMacro do
  defmacro greet(do: block) do
    quote do
      def hello(name), do: unquote(block)
    end
  end
end

defmodule Test do
  import MyMacro
  greet do
    IO.puts("Hello, #{name}!")
  end
end

尝试编译此代码会导致:

(CompileError) iex:6: undefined function name/0
(stdlib) lists.erl:1338: :lists.foreach/2
(stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
(iex) lib/iex/evaluator.ex:250: IEx.Evaluator.handle_eval/5

据我了解,这甚至在它到达我的宏之前就爆炸了,因为 elixir 在调用我的宏之前尝试为 do 块生成一个 AST,但name未定义。

我的目标是能够Test.hello("world")在编译 DSL 后调用。这在 Elixir 中可能吗?

标签: macroselixirmetaprogrammingdsl

解决方案


Elixir 的宏是卫生的,所以如果你在宏中声明一个变量,quote调用者将无法使用它。您可以通过使用以下包装变量的声明来禁用此功能var!

quote do
  def hello(var!(name)), do: unquote(block)
end

推荐阅读