基本场景是这样的:我需要从数据库中加载文本,然后将该文本转换为 Elixir 模块(或 Erlang 模块),然后对其进行调用。该文本实际上与模块文件相同。所以这是热代码加载的一种形式。我想编译“文件”,然后加载生成的模块,然后调用它。稍后我会卸载它。唯一的区别是代码存在于数据库中而不是磁盘上的文件中。(并且在我编写将加载它的代码时它不存在。)
我知道 Erlang 支持热代码加载,但似乎专注于在磁盘上编译文件然后加载梁。我希望将其作为一个更动态的过程来执行,并且我不会替换正在运行的代码,而是加载代码,然后运行它,然后卸载它。
Elixir 中有几个工具可以在运行时评估代码。我正在尝试弄清楚如何使用它们,并且文档有点稀疏。
Code.compile_string(string, "nofile")
“返回一个元组列表,其中第一个元素是模块名称,第二个是它的二进制”。所以,现在我有了模块名称和它们的二进制文件,但我不知道如何将二进制文件加载到运行时并调用它们。我该怎么做?(我可以看到代码库中没有相应的功能。)
可能我可以使用 Erlang 函数:
:code.load_binary(Module, Filename, Binary) ->
{module, Module} | {error, What}
好的,所以这会返回一个带有原子“模块”的元组,然后是模块。如果从数据库加载的字符串定义了一个名为“Paris”的模块,那么我将如何在我的代码中执行
paris.handler([parameters])
因为我事先不知道会有一个名为 paris 的模块?我可以知道,通过将字符串“paris”也存储在数据库中,这是名称,但是有没有办法调用模块,使用字符串作为您正在调用的模块的名称?
还有:
eval(string, binding // [], opts // [])
它评估字符串的内容。这个字符串可以是模块的完整定义吗?看来不是。我希望能够编写这个正在评估的代码,它具有多个相互调用的函数 - 例如一个完整的小程序,具有预定义的入口点(这可能是一个主要的,比如作为“DynamicModule.handle([参数,列表])”
然后是 EEx 模块,它有:
compile_string(source, options // [])
这对于制作模板非常有用。但最终它似乎只适用于存在字符串并且您已将 Elixir 代码嵌入其中的用例。它在选项的上下文中评估字符串并生成一个字符串。我正在寻求将字符串编译成一个或多个我可以调用的函数。(如果我只能制作一个很好的功能,那么该功能可以进行模式匹配或切换到做其他需要的事情......)
我知道这是非常规的,但我有这样做的理由,而且他们是好的。我正在寻找有关如何执行此操作的建议,但不需要被告知“不要那样做”。看起来应该是可能的,Erlang 支持热代码加载,并且 Elixir 非常动态,但我只是不知道语法或正确的功能。我会密切关注这个问题。提前致谢!
基于第一个答案的编辑:
感谢您的回答,这是一个很好的进展。正如 Yuri 所展示的, eval 可以定义一个模块,并且正如 José 指出的那样,我可以将代码 eval 用于带有绑定的一小部分代码。
正在评估的代码(无论是否转换为模块)将相当复杂。它的开发最好涉及将其分解为函数并调用这些函数。
为了提供帮助,让我提供一些背景信息。假设我正在构建一个 Web 框架。从数据库加载的代码是特定 URI 的处理程序。所以,当一个 HTTP 调用进来时,我可能会加载 example.com/blog/ 的代码。这个页面可能涉及几个不同的东西,比如评论、最近的帖子列表等。
由于很多人同时点击该页面,我正在生成一个进程来处理每个页面视图。因此,对于不同的请求,可以多次同时评估此代码。
模块解决方案允许将代码分解为页面不同部分的函数(例如:帖子列表、评论等),并且我会在启动时加载模块一次,并让许多进程产生该调用进去。该模块是全局的,对吗?
如果已经定义了一个模块会发生什么?EG:当模块发生变化时,已经有进程调用了该模块。
在 iex 中,我可以重新定义已经定义的模块:
iex(20)> Code.eval "defmodule A do\ndef a do\n5\nend\nend"
nofile:1: redefining module A
如果我在运行时将模块重新定义到当前调用该模块的所有进程,会发生什么?此外,这种重新定义是否会在 iex 之外正常运行?
假设重新定义模块会有问题,并且全局模块可能会遇到命名空间冲突的问题,我研究了使用 eval 来定义函数。
如果我只能让数据库中的代码定义函数,那么这些函数在任何进程的范围内,并且我们没有全局冲突的可能性。
但是,这似乎不起作用:
iex(31)> q = "f = function do
...(31)> x, y when x > 0 -> x+y
...(31)> x, y -> x* y
...(31)> end"
"f = function do\nx, y when x > 0 -> x+y\nx, y -> x* y\nend"
iex(32)> Code.eval q
{#Fun<erl_eval.12.82930912>,[f: #Fun<erl_eval.12.82930912>]}
iex(33)> f
** (UndefinedFunctionError) undefined function: IEx.Helpers.f/0
IEx.Helpers.f()
erl_eval.erl:572: :erl_eval.do_apply/6
src/elixir.erl:110: :elixir.eval_forms/3
/Users/jose/Work/github/elixir/lib/iex/lib/iex/loop.ex:18: IEx.Loop.do_loop/1
iex(33)> f.(1,3)
** (UndefinedFunctionError) undefined function: IEx.Helpers.f/0
IEx.Helpers.f()
erl_eval.erl:572: :erl_eval.do_apply/6
erl_eval.erl:355: :erl_eval.expr/5
src/elixir.erl:110: :elixir.eval_forms/3
/Users/jose/Work/github/elixir/lib/iex/lib/iex/loop.ex:18: IEx.Loop.do_loop/1
我也试过:
iex(17)> y = Code.eval "fn(a,b) -> a + b end"
{#Fun<erl_eval.12.82930912>,[]}
iex(18)> y.(1,2)
** (BadFunctionError) bad function: {#Fun<erl_eval.12.82930912>,[]}
erl_eval.erl:559: :erl_eval.do_apply/5
src/elixir.erl:110: :elixir.eval_forms/3
/Users/jose/Work/github/elixir/lib/iex/lib/iex/loop.ex:18: IEx.Loop.do_loop/1
所以,总结一下:
当有进程调用模块时,可以使用 Code.eval 重新定义模块吗?
是否可以使用 Code.eval 来制作范围绑定到调用 Code.eval 的进程的函数?
如果你明白我想要做什么,你能建议一个更好的方法吗?
另外,如果有更好的论坛我应该问这个问题,请随时告诉我。如果有我应该阅读的文档或相关示例,请随时向我指出。我不是想让你做所有的工作,我只是自己无法弄清楚。
我正在学习 Elixir 专门用于动态评估代码的能力,但我的 Elixir 知识现在很少——我刚刚开始——而且我的 erlang 也生疏了。
非常感谢!