【问题标题】:Elixir: eval'ing code in module context using Code.eval_quoted/3Elixir:使用 Code.eval_quoted/3 在模块上下文中评估代码
【发布时间】:2016-04-17 20:39:42
【问题描述】:

给定以下模块:

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 调用其他函数/宏。此外,我不明白import 语句如何/为什么起作用:当我已经在Foo 的上下文中时,为什么我需要import Foo

这里发生了什么?感谢您的帮助!

【问题讨论】:

    标签: metaprogramming elixir


    【解决方案1】:

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

    【讨论】:

    • 我读过(大部分)元编程长生不老药;这就是我提出这个问题的方式...... :) 所以你的例子(我猜是书中的例子)使用Code.eval_quoted/3 没有第三个参数(我感兴趣的那个),所以如果你把IO.inspect __MODULE__在引用的块中,您将获得nil。我将Foo.eval/0__ENV__ 传递给Code.eval_quoted/3,所以如果我打印__MODULE__,我会得到Foo。这让我相信我的块正在 Foo 模块的上下文中被评估,因此可以访问它的所有功能......但它没有。
    • 我编辑了我的答案以解释为什么你不能访问所有功能以及为什么传递 __ENV__ 并不能在新环境中设置所有内容。
    猜你喜欢
    • 2010-11-24
    • 1970-01-01
    • 1970-01-01
    • 2020-08-12
    • 2018-06-08
    • 2011-01-17
    • 2021-09-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多