Elixir:使用代码在模块上下文中评估代码。eval_quoted/3
Elixir: eval'ing code in module context using Code.eval_quoted/3
给定以下模块:
defmodule Foo do
def bar do
IO.puts "I'm bar"
end
def eval(quoted_code) do
Code.eval_quoted(quoted_code, [], __ENV__)
end
end
然后下面的代码像预期的那样输出 Foo
:
# Outputs Foo
quote do
IO.inspect __MODULE__
end |> Foo.eval
那么为什么我不能从引用块中调用 Foo
中的其他函数?例如,下面的代码给了我一个 CompileError
:
# CompileError: undefined function bar/0
quote do
bar
end |> Foo.eval
但以下代码有效:
# Outputs "I'm bar"
quote do
import Foo
bar
end |> Foo.eval
import
语句到底在做什么?将 Foo
导入 Foo
?这对我来说没有意义。
据我了解,Foo.eval
正在评估 Foo
模块上下文中引用的代码。但显然那是错误的,因为我不能在没有前缀 Foo.
的情况下从 Foo 调用其他 functions/macros。此外,我不明白 how/why import
语句的作用:Why do I need to import Foo
when I already in the context of Foo
?
这是怎么回事?感谢您的帮助!
Elixir 中的宏卫生。这意味着,他们不使用调用者上下文。让我们看一个来自 "Metaprogramming Elixir" book
的例子
number = 5
ast = quote do
number * 10
end
Code.eval_quoted ast
** (CompileError) nofile:1: undefined function number/0
第二个:
number = 5
ast = quote do
unquote(number) * 10 #the only change is unquote here
end
Code.eval_quoted ast
{50, []}
这个想法是,您可以在编译时计算一些东西,然后(使用宏)将这个值注入到生成的代码中。宏可以使用与您的代码相同的变量名,并且它们 不会 冲突。如果您知道自己在做什么并且真的想从外部范围访问变量,则可以使用 var!
.
你正试图做相反的事情。您想在宏中使用代码中的内容。这会使您的宏不可重复使用。
一开始使用 import 看起来是个好主意,表明您的宏依赖于这个特定模块。由于那条线,它将在任何地方工作。
[编辑以回答评论]:
当您将 __ENV__
作为最后一个参数传递时,您不会从环境中导入所有内容。只能设置那些选项,它们是:
:file
:line
:aliases
:requires
:macros
问题是,环境还有其他密钥。我们要覆盖的一个是 context_modules
。这将允许从模块内部调用东西。恐怕做不到。这是一个设计决定。
在旧版本的 elixir 中(我检查了 1.0),有额外的选项 :delegate_locals_to
,但它在 1.2 中不存在。
给定以下模块:
defmodule Foo do
def bar do
IO.puts "I'm bar"
end
def eval(quoted_code) do
Code.eval_quoted(quoted_code, [], __ENV__)
end
end
然后下面的代码像预期的那样输出 Foo
:
# Outputs Foo
quote do
IO.inspect __MODULE__
end |> Foo.eval
那么为什么我不能从引用块中调用 Foo
中的其他函数?例如,下面的代码给了我一个 CompileError
:
# CompileError: undefined function bar/0
quote do
bar
end |> Foo.eval
但以下代码有效:
# Outputs "I'm bar"
quote do
import Foo
bar
end |> Foo.eval
import
语句到底在做什么?将 Foo
导入 Foo
?这对我来说没有意义。
据我了解,Foo.eval
正在评估 Foo
模块上下文中引用的代码。但显然那是错误的,因为我不能在没有前缀 Foo.
的情况下从 Foo 调用其他 functions/macros。此外,我不明白 how/why import
语句的作用:Why do I need to import Foo
when I already in the context of Foo
?
这是怎么回事?感谢您的帮助!
Elixir 中的宏卫生。这意味着,他们不使用调用者上下文。让我们看一个来自 "Metaprogramming Elixir" book
的例子number = 5
ast = quote do
number * 10
end
Code.eval_quoted ast
** (CompileError) nofile:1: undefined function number/0
第二个:
number = 5
ast = quote do
unquote(number) * 10 #the only change is unquote here
end
Code.eval_quoted ast
{50, []}
这个想法是,您可以在编译时计算一些东西,然后(使用宏)将这个值注入到生成的代码中。宏可以使用与您的代码相同的变量名,并且它们 不会 冲突。如果您知道自己在做什么并且真的想从外部范围访问变量,则可以使用 var!
.
你正试图做相反的事情。您想在宏中使用代码中的内容。这会使您的宏不可重复使用。
一开始使用 import 看起来是个好主意,表明您的宏依赖于这个特定模块。由于那条线,它将在任何地方工作。
[编辑以回答评论]:
当您将 __ENV__
作为最后一个参数传递时,您不会从环境中导入所有内容。只能设置那些选项,它们是:
:file
:line
:aliases
:requires
:macros
问题是,环境还有其他密钥。我们要覆盖的一个是 context_modules
。这将允许从模块内部调用东西。恐怕做不到。这是一个设计决定。
在旧版本的 elixir 中(我检查了 1.0),有额外的选项 :delegate_locals_to
,但它在 1.2 中不存在。