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 中不存在。