长生不老药! ...如何从调用者的范围中读取变量
Elixir var! ... How to read a variable from caller's scope
抱歉,如果有人问过这个问题。在论坛中搜索 var!
,我得到了所有带有 var
字样的 post。很难缩小范围。
努力编写一个从调用者的上下文中读取变量并从函数中 returns 读取变量的宏。这是我能想到的最简单的问题形式:
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 的元编程书籍、此博客 post (https://www.theerlangelist.com/article/macros_6) 和许多其他书籍。看起来应该可以,但我就是想不通为什么不行..
查看这些文档:https://hexdocs.pm/elixir/1.12/Kernel.SpecialForms.html#quote/2
有很多例子。 my_var
仅在 quote
块内定义,但不在 quote
块内的 def
函数内定义。
你可以这样做:
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()
并在 quote
块内调用 var!
,分配给模块属性 @my_var
。
但我不太擅长元编程,可能还有其他人可以更好地回答它。
如果您已经了解 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
希望其中的一些想法对您的情况有用。
Kernel.var!/2
宏并不如您所想。
var!/2
的唯一目的是将变量标记为宏卫生。这意味着,使用 var!/2
可能会更改外部(关于当前上下文)范围内的变量值。在您的示例中,有 两个 范围(defmacro[create_get_function]
和 def[get_my_var]
)要绕过,这就是 my_var
无法通过的原因。
整个问题看起来像 XY-Problem. It looks like you want to declare kinda compile-time variable and modify it all way through the module code. For this purpose we have module attributes 和 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"
。发生这种情况是因为 var!(my_var_inner) = 42
调用将一直保持到运行时,并且在这里绕过宏卫生将是一个空操作。
抱歉,如果有人问过这个问题。在论坛中搜索 var!
,我得到了所有带有 var
字样的 post。很难缩小范围。
努力编写一个从调用者的上下文中读取变量并从函数中 returns 读取变量的宏。这是我能想到的最简单的问题形式:
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 的元编程书籍、此博客 post (https://www.theerlangelist.com/article/macros_6) 和许多其他书籍。看起来应该可以,但我就是想不通为什么不行..
查看这些文档:https://hexdocs.pm/elixir/1.12/Kernel.SpecialForms.html#quote/2
有很多例子。 my_var
仅在 quote
块内定义,但不在 quote
块内的 def
函数内定义。
你可以这样做:
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()
并在 quote
块内调用 var!
,分配给模块属性 @my_var
。
但我不太擅长元编程,可能还有其他人可以更好地回答它。
如果您已经了解 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
希望其中的一些想法对您的情况有用。
Kernel.var!/2
宏并不如您所想。
var!/2
的唯一目的是将变量标记为宏卫生。这意味着,使用 var!/2
可能会更改外部(关于当前上下文)范围内的变量值。在您的示例中,有 两个 范围(defmacro[create_get_function]
和 def[get_my_var]
)要绕过,这就是 my_var
无法通过的原因。
整个问题看起来像 XY-Problem. It looks like you want to declare kinda compile-time variable and modify it all way through the module code. For this purpose we have module attributes 和 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"
。发生这种情况是因为 var!(my_var_inner) = 42
调用将一直保持到运行时,并且在这里绕过宏卫生将是一个空操作。