在Elixir或Erlang中如何在运行时动态创建和加载模块? [英] How do you create and load modules dynamically at runtime in Elixir, or Erlang?

查看:151
本文介绍了在Elixir或Erlang中如何在运行时动态创建和加载模块?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

基本的情况是这样的:我需要从数据库加载文本,然后将该文本转换为Elixir模块(或Erlang模块),然后调用它。该文本与模块文件实际上相同。所以这是一种热代码加载的形式。我想编译文件,然后加载生成的模块,然后调用它。后来我会卸载它。唯一的区别是数据库中存在代码,而不是磁盘上的文件。 (并且在我正在编写将要加载的代码的时候不存在)。



我知道Erlang支持热代码加载,但似乎集中在磁盘上编译文件,然后加载光束。我希望这样做是一个更动态的过程,我不会替换运行的代码,而是加载代码,然后运行它,然后卸载它。



是Elixir中运行时评估代码的几个设施。我试图找出如何做到这一点,文件有点稀疏。

  Code.compile_string(string,nofile)

返回第一个元素是模块名称的元组列表,第二个元素是其二进制。所以,现在我有模块名称和二进制文件,但是我不知道如何将二进制文件加载到运行时并调用它们。我该怎么办? (在代码库中没有任何功能,我可以看到。)



可能我可以使用Erlang函数:

 :code.load_binary(Module,Filename,Binary) - > 
{module,Module} |好的,所以这样返回一个元素与原子模块,然后再返回一个元组。模块。如果从数据库加载的字符串定义了一个名为Paris的模块,那么在我的代码中如何执行

  paris.handler ([parameters])

由于我不知道会有一个叫做巴黎的模块?我可以知道,通过将字符串paris也存储在数据库中,这是名称,但是有没有任何方式调用模块,使用字符串作为您调用的模块的名称?



还有:

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

其中评估字符串的内容。这个字符串可以是模块的整个定义吗?看来没有。我想能够编写正在评估的代码,使其具有多个彼此调用的功能 - 例如。一个完整的小程序,具有预定义的入口点(可以是主要的,如DynamicModule.handle([parameter,list])



然后有这个EEx模块有:

  compile_string(source,options // [])

哪个是很好的做模板,但最终它似乎只适用于有一个字符串的用例,你已经有Elixir代码嵌入在它中,它评估字符串在选项的上下文中,并产生一个字符串,我正在寻求将字符串编译成一个或多个函数,然后我可以调用(如果我只能使一个函数很好,那个函数可以模式匹配或切换到其他需要的东西....)



我知道这是非常规的,但我有理由这样做,他们是好的,我正在寻找如何做这个的建议,但不需要被告知不要这样做,似乎应该是可以的可以,Erlang支持热代码加载,而Elixir是非常动态的,但我只是不知道语法或正确的功能。我会密切关注这个问题。感谢提前!






基于第一个答案的EDITS:

$ b感谢您的答案,这是一个很好的进展。如Yuri所示,eval可以定义一个模块,José指出,我可以使用代码eval来处理绑定的小部分代码。



正在评估的代码是否变成模块,否则)会变得相当复杂。它的发展最好是将其分解为功能并调用这些功能。



为了帮助,让我提供一些上下文。假设我正在构建一个Web框架。从数据库加载的代码是特定URI的处理程序。所以,当一个HTTP调用进来时,我可能会加载example.com/blog/的代码。这个页面可能涉及几个不同的东西,如评论,最近的帖子列表等。



由于很多人都在同一时间点击页面,所以我正在产生一个处理每个页面视图的过程。因此,有不同的请求可以同时评估此代码。



该模块解决方案允许将代码分解为不同部分的代码页面(例如:帖子,评论等)。我将在启动时加载模块一次,并让许多进程产生一个调用。该模块是全球的,正确的?



如果已经定义了一个模块,会发生什么? EG:当模块发生变化,并且有进程已经调用该模块。



在iex中,我可以重新定义已经定义的模块:

  IEX(20)> Code.evaldefmodule A do\\\
def a do\\\
5\\\
end\\\
end
nofile:1:重新定义模块A

如果在运行时将模块重定义到当前调用该模块的所有进程,会发生什么?此外,这种重新定义在正常操作之外是否在iex之外工作?



假设重新定义模块是有问题的,并且全局的模块可能会遇到名称空间冲突的问题,我研究了使用eval定义一个函数。



如果我只能从数据库中定义函数的代码,那么这些函数在任何进程的范围内,我们没有全球碰撞的可能性。



然而,这似乎不起作用:

  iex (31)> q =f = function do 
...(31)> x,y当x> 0 - > x + y
...(31)> x,y - > ; x * y
...(31)> end
f = function do \\\
x,y当x> 0> x + y \\\
x时,y - > ; x * y\\\
end
iex(32)> Code.eval q
{#Fun< erl_eval.12.82930912>,[f:#Fun< erl_eval.12.82930912>]}
iex(33) f
**(UndefinedFunctionError)未定义函数: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.evalfn(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. 如果你明白我想要做什么,你可以建议一个更好的方法吗?


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



我正在学习Elixir专门用于动态排除代码的能力,但是我的Elixir知识现在很少 - 我刚刚开始 - 而且我的erlang也是生锈的。



非常感谢!

解决方案

正如你所描述的,最终可以采用不同的方法将其归结为两个不同的类别:1)代码编译和2)代码评估。您上面描述的示例需要编译,这将定义一个模块,然后您必须调用它。但是,正如您所发现的那样,它需要定义一个模块名称,并可能在数据库更改时清除和丢弃这些模块。另外请注意,定义模块可能会耗尽原子表,因为为每个模块创建一个原子。我只会使用这种方法,如果你需要最多编译十几个模块。



(一个小笔记, Code.compile_string 已经定义了模块,所以您不需要调用 load_binary )。



也许更简单方法是调用 Code.eval 从数据库传递代码,从而进行代码评估。如果您想要评估一些自定义规则,它可以正常工作。 Code.eval 接受绑定,这将允许您将参数传递给代码。我们假设你有一个a + b存储在数据库中,其中 a b 是参数,你可以评价为:

  Code.evala + 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 = string_to_ast!(record.body)
module = Module.concat(FromDB,Data#{record.id})
Module.create module,contents,[]
module

其中记录是从数据库中获取的任何内容。假设记录ID是 1 ,它将定义一个模块 FromDB.Data1 ,然后你可以调用 foo bar



关于代码重新加载, code> defmodule Module.create 使用:code.load_binary 加载模块。这意味着如果您更新模块,旧版本仍然保持不变,直到另一个新版本加载。



您还应该添加的其中一个是缓存过期,所以您不需要在每个请求上编译一个模块。如果您在每次更改记录内容时增加数据库中的lock_version,则可以进行此操作。最终的代码将是这样的:

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

如果编译执行
id = record.id
contents = Code.string_to_ast!(record.body)
contents = quote do
def lock_version,do:unquote(record.lock_version)
unquote(contents)
end
Module.create module,contents,[]
end

模块


The basic scenario is this: I need to load text from a database, and then turn that text into an Elixir module (or an Erlang module) and then make calls into it. The text is effectively the same as a module file. So this is a form of hot code loading. I want to compile the "file" and then load the resulting module, then make calls into it. Later I'll unload it. The only difference is the code exists in a database rather than a file on the disk. (and it doesn't exist at the time that I'm writing the code that will be loading it.)

I know Erlang supports hot code loading, but seems focused on compiling files on disk and then loading the beams. I wish to do this as a more dynamic process, and I won't be replacing running code, but loading the code, then running it, then unloading it.

There are several facilities in Elixir for evaluating code at runtime. I'm trying to figure out how to do this with them, and the documentation is a bit sparse.

Code.compile_string(string, "nofile")

"returns a list of tuples where the first element is the module name and the second one is its binary". So, now I have the modules names and their binaries, but I do not know of a way to then load the binaries into the runtime and call into them. How would I do that? (There's no function for that in the Code library that I can see.)

Possibly I could then use the Erlang function:

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

Ok, so this returns a tuple with the atom "module" and then the Module. If the string loaded from the database defined a module called "Paris", how in my code would I then execute

paris.handler([parameters])

since I don't know in advance that there will be a module called paris? I could know, by having the string "paris" also stored in the database that this is the name, but is there any way of calling into a module, using a string as the name of the module you're calling?

There is also:

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

Which evaluates the contents of the string. Can this string be the entire definition of a module? It appears not. I'd like to be able to write this code that's being evaluated in such a way that it has multiple functions that call each other--e.g. a complete little program, with a pre-defined entry point (Which could be a main, such as "DynamicModule.handle([parameter, list])"

Then there's the EEx module, which has:

compile_string(source, options // [])

Which is great for doing templates. But ultimately it only seems to work for the use case where there's a string and you've got Elixir code embedded in it. It evaluates the string in the context of the options and produces a string. I'm seeking to compile the string into one or more functions that I can then call. (If I can only make one function that's fine, that function can pattern match or switch into doing the other things that are needed....)

I know this is unconventional, but I have my reasons for doing it this way and they are good ones. I'm looking for advice about how to do this, but don't need to be told "don't do that". It seems like it should be possible, Erlang supports hot code loading and Elixir is pretty dynamic, but I just don't know the syntax, or the right functions. I'll be monitoring this question closely. Thanks in advance!


EDITS based on the first answers:

Thanks for the answers, this is good progress. As Yuri showed, eval can define a module, and as José points out, I can just use code eval for small parts of code with bindings.

The code being evaluated (whether turned into a module, or not) is going to be fairly complex. And its development would be best involving breaking it down into functions and calling those functions.

To help, let me provide some context. Assume I'm building a web framework. The code loaded in from the database is handlers for specific URIs. So, when an HTTP call comes in, I might load the code for example.com/blog/ This page might involve several different things, such as comments, a list of recent posts, etc.

Since many people are hitting the page at the same time, I'm spawning a process to handle each page view. Thus there are many times when this code may be evaluated simultaneously, for different requests.

The the module solution allows one to break the code up into functions for different parts of the page (eg: the list of posts, comments, etc. ) And I would load the module once, at startup, and let many processes spawn that call into it. The module is global, correct?

What happens if there's a module already defined? EG: When the module changes, and there are processes already calling that module.

In iex, I am able to redefine a module that's already been defined:

iex(20)> Code.eval "defmodule A do\ndef a do\n5\nend\nend"
nofile:1: redefining module A

What happens if I redefine the module at runtime, to all the processes currently calling into that module? Also, will this redefinition work outside of iex, in normal operation?

Assuming that redefining the module would be problematic, and that modules being global might run into problems with namespace collisions, I looked into using eval to define a function.

If I can merely have the code from the database define functions, then those functions are within the scope of whatever process, and we don't have the possibility of global collisions.

However, this doesn't seem to work:

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

I also tried:

    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

So, in summary:

  1. Can modules be redefined using Code.eval when there are processes calling into them?

  2. Is it possible to use Code.eval to make functions whose scope is bound to the process in which Code.eval was called?

  3. If you understand what it is I'm trying to do, can you suggest a better way to go about it?

Also, if there is a better forum where I should be asking this, feel free to let me know. And if there are docs or relevant examples I should read, please feel free to point me to them. I'm not trying to get you to do all the work, I just am unable to figure it out myself.

I'm learning Elixir specifically for the ability to dynamically evlauate code, but my Elixir knowledge is minimal now- I just started- and my erlang is rusty too.

Much thanks!

解决方案

As you described, there are many different approaches you could take by ultimately they boil down to two different categories: 1) code compilation and 2) code evaluation. The example you described above requires compilation, which will define a module and then you would have to invoke it. However, as you found out, it requires defining a module name and potentially purging and discarding those modules when the database changes. Also, note that, defining modules may exhaust the atom table, as an atom is created for every module. I would only use this approach if you need to compile at maximum a dozen modules.

(A small note, Code.compile_string already defines the module, so you don't need to call load_binary).

Maybe a simpler approach is to call Code.eval passing the code from the database, thus code evaluation. It works fine if all you want to is to evaluate some custom rules. The Code.eval accepts a binding, which would allow you to pass parameters to the code. Let's suppose you have "a + b" stored in the database, where a and b are the parameters, you could evaluate it as:

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

EDIT BASED ON QUESTION'S EDITS

If you are evaluating code, keep in mind that Code.eval returns the result of evaluating the code and the new binding, so your example above would be better written as:

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

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

However, given your use case, I wouldn't consider using eval but I would indeed compile a module. Since the information is coming from the database, you can use this fact to generate unique modules per record for you. For example, you can assume developers will be adding the contents of a module to the database, something like:

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

After you get this information from the database, you can compile it using:

record   = get_record_from_database
id       = record.id
contents = Code.string_to_ast!(record.body)
module   = Module.concat(FromDB, "Data#{record.id}")
Module.create module, contents, []
module

Where record is anything that you get back from the database. Assuming the record id is 1, it would define a module FromDB.Data1 which then you would be able to invoke foo and bar.

Regarding code reloading, both defmodule and Module.create use :code.load_binary to load the module. Which means that if you update the module, the old version is still kept around until another new version is loaded.

One of the things you should add as well is cache expiration, so you don't need to compile a module on every request. This can be done if you have a lock_version in the database that you increment every time you change the record contents. The final code would be something like:

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

if compile do
  id       = record.id
  contents = Code.string_to_ast!(record.body)
  contents = quote do
    def lock_version, do: unquote(record.lock_version)
    unquote(contents)
  end
  Module.create module, contents, []
end

module

这篇关于在Elixir或Erlang中如何在运行时动态创建和加载模块?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆