0

对不起,如果这已经被问到了。在论坛中搜索var!给了我所有带有 word 的帖子var。很难缩小范围。

努力编写一个宏,该宏从调用者的上下文中读取一个变量并从函数中返回它。这是我能想到的最简单的问题形式:

defmodule MyUnhygienicMacros do

  defmacro create_get_function do
    quote do
      def get_my_var do
        var!(my_var)
      end
    end
  end

end

defmodule Caller do

  require MyUnhygienicMacros

  my_var = "happy"

  MyUnhygienicMacros.create_get_function()

end

目标是在我运行 iex 会话时看到这一点:

$ Caller.get_my_var()
"happy"

但这不会编译。来电者my_var也未使用。

编译错误expected "my_var" to expand to an existing variable or be part of a match.

我已经阅读了 McCord 的元编程书、这篇博文 ( https://www.theerlangelist.com/article/macros_6 ) 以及许多其他内容。似乎它应该工作,但我只是不知道为什么它不会..

4

3 回答 3

1

Kernel.var!/2宏不做你认为它做的事。

的唯一目的var!/2是标记变量关闭宏观卫生。这意味着,使用var!/2one 可能会更改外部(相对于当前上下文)范围内的变量值。在您的示例中,有两个范围(defmacro[create_get_function]def[get_my_var])可以绕过,这就是为什么my_var无法通过。

整个问题看起来像一个XY-Problem。看起来你想声明一个编译时变量并通过模块代码修改它。为此,我们有带有accumulate: true.

如果你想简单地在 中使用这个变量create_get_function/0,就unquote/1它。如果要累积值,请使用模块属性。如果您最终仍想保持自己的方式,请将本地编译时变量传递给两个范围,两次破坏卫生。

defmodule MyUnhygienicMacros do
  defmacro create_get_function do
    quote do
      my_var_inner = var!(my_var)
      def get_my_var, do: var!(my_var_inner) = 42
      var!(my_var) = my_var_inner
    end
  end
end

defmodule Caller do
  require MyUnhygienicMacros
  my_var = "happy"
  MyUnhygienicMacros.create_get_function()
  IO.inspect(my_var, label: "modified?")
end

请注意,与您预期的不同,上面的代码仍然modified?: "happy" 在 compile-time 期间打印。发生这种情况是因为var!(my_var_inner) = 42调用将一直保持到运行时,并且在这里绕过宏卫生将是无操作的。

于 2021-09-21T05:14:33.910 回答
0

看看这些文档:https ://hexdocs.pm/elixir/1.12/Kernel.SpecialForms.html#quote/2

有很多例子。my_var仅在块内定义,但quote不在块内的def函数内定义quote

你可以这样做:

defmodule MyUnhygienicMacros do
  defmacro create_get_function() do
    quote do
      @my_var var!(my_var)
      def get_my_var do
        @my_var
      end
    end
  end
end

defmodule Caller do
  require MyUnhygienicMacros
  my_var = "happy"
  MyUnhygienicMacros.create_get_function()
end

Caller.get_my_var()
|> IO.inspect()

var!quote块内调用,分配给模块属性@my_var

但是我不太擅长元编程,可能还有其他人可以更好地回答它。

于 2021-09-20T18:28:34.803 回答
0

如果您已经了解 Elixir 中的惯用语以及可能违背规律的内容,请原谅我。我提出这个答案是希望它针对您的问题的精神。

首先,Elixir 中的所有内容都是一个赋值,因此在大多数情况下,不可能在创建它们的范围之外读取变量。在我做 OO 编程的日子里,最难忘记的事情可能是简单的模式(它看起来很像你问题中的代码):

# pseudo-code
x = "something"
foreach y in x {
  x = "something new"
}

这种类型的结构在 Elixir 中不起作用——您经常需要使用一些 map 或 reduce 函数来完成等效的结果。您可能可以通过使用宏来绕过此限制,但可能必须有一个非常好的理由。因此,也许您应该重新考虑为什么需要这样的结构,或者您至少可以分享理由,以便其他阅读您的问题的人清楚。

其次,考虑在需要值时将参数传递给宏——这有助于使范围更明显。您可以利用unquote来访问它们,例如

defmodule Foo do
  defmacro __using__(opts) do
    quote do 
      def get_thing(), do: unquote(opts[:thing])
    end
  end
end

以便

defmodule Bar do
  use Foo, thing: "blort"
end

defmodule Glop do
  use Foo, thing: "eesh"
end

将允许您执行以下操作:

Bar.get_thing() |> IO.puts() # "blort"
Glop.get_thing() |> IO.puts() # "eesh"

我会得出结论,在宏中做任何过于花哨或聪明的事情都会使它们难以调试和维护。对于它的价值,我通常发现最好保持宏“瘦”(如 MVC 应用程序中的瘦控制器):根据我的经验,当它们将它们移交给其他地方的另一个常规功能时,它们的效果最好,例如

  defmacro __using__(opts) do
    quote do
      def thing(x), do: Verbose.thing(unquote(opts[:arg1]), unquote(opts[:arg2]), x)

    end
  end   

希望那里的一些想法对您的情况有用。

于 2021-09-20T20:54:47.493 回答