如何创建并加载模块在运行时动态的药剂,或者二郎?(How do you create and loa

2019-07-03 13:45发布

基本情况是这样的:我需要从数据库加载文本,然后打开该文本为药剂模块(或一个Erlang模块),然后拨打电话到它。 该文本实际上是相同的模块文件。 因此,这是热码负载的形式。 我要编译的“文件”,然后加载生成的模块,然后拨打电话到它。 后来我就卸载它。 唯一的区别是一个数据库,而不是磁盘上的文件存在的代码。 (和它不会在我正在写将加载它的代码的时间存在。)

我知道二郎支持热代码加载,但似乎集中在编制磁盘上的文件,然后加载横梁。 我希望这样做,因为一个更加动态的过程,我不会被替换的运行代码,但加载代码,然后运行它,然后卸载它。

有几个设施药剂在运行时评估代码。 我试图找出如何与他们做到这一点,该文档是有点稀疏。

Code.compile_string(string, "nofile")

“其中的第一个元素是模块名,第二个是其二进制返回元组的列表”。 所以,现在我拥有的这些模块的名称和它们的可执行文件,但我不知道的方式来然后加载到二进制运行时和调用到他们。 我会怎么做呢? (有针对的代码库,我可以看到没有作用。)

也许我可以再使用Erlang的功能:

:code.load_binary(Module, Filename, Binary)  ->
           {module, Module} | {error, What}

好了,所以这将返回与该原子“模块”,然后模块的元组。 如果从数据库加载字符串定义了一个名为“巴黎”模块,在我的代码我将如何再执行

paris.handler([parameters])

因为我不可能提前知道这样做会有一个叫做巴黎模块? 我能知道,通过具有字符串“巴黎”,也存储在数据库中,这是名字,但有叫成模块,使用字符串作为你调用该模块的名称的方法吗?

此外还有:

eval(string, binding // [], opts // [])

其分析字符串的内容。 这个字符串可以是一个模块的整个定义是什么? 好像还没有。 我希望能写出这样的代码,在这样的方式进行评估,它有相互调用多种功能 - 例如,一个完整的小程序,用一个预先定义的入口点(这可能是一个主要的,如为 “DynamicModule.handle([参数,列表])”

然后是的EEx模块,其中有:

compile_string(source, options // [])

这是伟大的做模板。 但最终似乎只对使用情况有一个字符串的工作,你已经有了嵌入了药剂代码。 它评估了选项的情况下串并产生一个字符串。 我正在寻找字符串编译成一个或多个功能,我可以再调用。 (如果我只能做一个功能的罚款,即功能模式匹配或者切换到这样做是需要的其他东西......)

我知道这是标新立异,但我有我的理由做这种方式,他们是好的。 我正在寻找关于如何做到这一点的建议,但不需要被告知“不这样做”。 现在看来似乎应该是可能的,二郎神支持热代码加载和药剂是非常动态的,但我只是不知道语法,或右功能。 我会密切关注这个问题。 提前致谢!


EDITS基于第一答案:

感谢您的答案,这是很好的进展。 成宥利则显示,EVAL可以定义一个模块,何塞指出,我可以只使用代码的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

因此,在总结:

  1. 可以使用的模块时Code.eval有过程调用到它们被重新定义?

  2. 是否有可能使用Code.eval作出其范围必将过程中Code.eval被称为功能呢?

  3. 如果你了解什么是我想要做的,你可以提出一个更好的路要走呢?

此外,如果有更好的论坛,我应该问这个,随时让我知道。 如果有文档或相关的例子我应该阅读,请随时点我给他们。 我不想让你做的所有工作,我只是一时无法找出自己。

我正在学习药剂专为能够动态evlauate代码,但我的药剂知识是最小的现在 - 我只是started-和我Erlang是生锈了。

非常感谢!

Answer 1:

正如你所描述的,还有你可以采取由他们最终归结为两个不同的类别许多不同的方法:1)代码编译和2)代码的评价。 你上述的例子中,需要编译,这将定义一个模块,然后你将不得不调用它。 然而,当你发现了,它需要定义模块的名称和潜在的清洗和丢弃这些模块数据库的变化时。 此外,请注意,定义模块可能会耗尽原子表,作为每个模块创建的原子。 如果你需要在最大十几个模块来编译我只会用这种方法。

(小记, Code.compile_string已经定义模块,这样你就不需要调用load_binary )。

也许一个更简单的方法是调用Code.eval传递代码从数据库中,从而代码评估。 它工作正常,如果你想做的是评估一些自定义规则。 该Code.eval接受有约束力的,这样可以让您将参数传递给代码。 让我们假设你有“A + B”存储在数据库中,其中ab是参数,你可以评估它:

Code.eval "a + b", [a: 1, b: 1]

编辑基于问句的编辑

如果您正在评估代码,请记住, Code.eval返回评估代码和新绑定的结果,所以你上面的例子会更好的写法如下:

q = "f = function do
x, y when x > 0 -> x+y
x, y -> x* y
end"

{ _, binding } = Code.eval q
binding[:f].(1, 2)

然而,鉴于您的使用情况下,我不会考虑使用eval,但我确实会编译的模块。 由于信息是来自数据库,你可以利用这一点来生成每个记录独特的模组。 例如,你可以假设开发商将加入一个模块的内容数据库,是这样的:

def foo, do: 1
def bar, do: 1

你从数据库中获取此信息后,您可以使用编译:

record   = get_record_from_database
id       = record.id
contents = Code.string_to_quoted!(record.body)
module   = Module.concat(FromDB, "Data#{record.id}")
Module.create module, contents, Macro.Env.location(__ENV__)
module

其中记录是什么,你从数据库中获取回来。 假设记录ID为1 ,它会定义一个模块FromDB.Data1 ,然后你就可以调用foobar

关于代码重新加载,既defmoduleModule.create使用:code.load_binary加载模块。 这意味着,如果更新模块,老版本依然直到另一个新版本被加载保持周围。

一个你应该添加为好东西是缓存过期,所以你不需要编译模块在每次请求。 这可以,如果你有,你增加每次更改记录内容时数据库中的lock_version来完成。 最终代码会是这样的:

record  = get_record_from_database
module  = Module.concat(FromDB, "Data#{record.id}")
compile = :code.is_loaded(module) == false or
            record.lock_version > module.lock_version

if compile do
  id       = record.id
  contents = Code.string_to_quoted!(record.body)
  contents = quote do
    def lock_version, do: unquote(record.lock_version)
    unquote(contents)
  end
  Module.create module, contents, Macro.Env.location(__ENV__)
end

module


Answer 2:

Code.eval可用于定义一个模块:

iex(1)> Code.eval "defmodule A do\ndef a do\n1\nend\nend" 
{{:module,A,<<70,79,82,49,0,0,2,168,66,69,65,77,65,116,111,109,0,0,0,98,0,0,0,11,8,69,108,105,120,105,114,45,65,8,95,95,105,110,102,111,95,95,4,100,111,99,115,9,102,117,...>>,{:a,0}},[]}
iex(2)> A.a
1

这是否帮助?



Answer 3:

你检查: Dynamic Compile Library by Jacob Vorreuter 。 见下面的例子

  1>字符串= “-module(添加)\ n -export([添加/ 2])\ n添加(A,B) - > A + B. \ n” 个。  “ - 模(添加)\ n -export([添加/ 2])\ n添加(A,B) - > A + B. \ n” 个  2> dynamic_compile:load_from_string(字符串)。  {模块,添加}  3>中加入:添加(2,5)。  7  4> 
此外,检查出这个问题及其答案



文章来源: How do you create and load modules dynamically at runtime in Elixir, or Erlang?