dynamic module erlang elixir

dynamic - ¿Cómo se crean y cargan módulos dinámicamente en tiempo de ejecución en Elixir o Erlang?



module (3)

Code.eval se puede usar para definir un módulo:

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

¿Esto ayuda?

El escenario básico es el siguiente: necesito cargar texto desde una base de datos, y luego convertir ese texto en un módulo Elixir (o un módulo Erlang) y luego hacer llamadas a él. El texto es efectivamente el mismo que un archivo de módulo. Entonces esta es una forma de carga de código caliente. Quiero compilar el "archivo" y luego cargar el módulo resultante, luego realizar llamadas. Más tarde lo descargaré. La única diferencia es que el código existe en una base de datos en lugar de un archivo en el disco. (y no existe en el momento en que estoy escribiendo el código que lo va a cargar).

Sé que Erlang soporta la carga de código caliente, pero parece enfocado en compilar archivos en el disco y luego cargar los haces. Deseo hacer esto como un proceso más dinámico, y no reemplazaré el código en ejecución, sino que cargaré el código, lo ejecutaré y luego lo descargaré.

Hay varias instalaciones en Elixir para evaluar el código en tiempo de ejecución. Estoy intentando descubrir cómo hacer esto con ellos, y la documentación es un poco escasa.

Code.compile_string(string, "nofile")

"devuelve una lista de tuplas donde el primer elemento es el nombre del módulo y el segundo es su binario". Entonces, ahora tengo los nombres de los módulos y sus binarios, pero no sé de qué manera cargar los binarios en el tiempo de ejecución y llamarlos. ¿Como podría hacerlo? (No hay ninguna función para eso en la biblioteca de códigos que pueda ver).

Posiblemente podría usar la función de Erlang:

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

Ok, entonces esto devuelve una tupla con el átomo "módulo" y luego el Módulo. Si la cadena cargada desde la base de datos definía un módulo llamado "Paris", ¿cómo en mi código iba a ejecutar?

paris.handler([parameters])

ya que no sé de antemano que habrá un módulo llamado parís Yo podría saber, al tener la cadena "paris" también almacenada en la base de datos, que este es el nombre, pero ¿hay alguna forma de llamar a un módulo, usando una cadena como el nombre del módulo que está llamando?

También hay:

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

Que evalúa el contenido de la cadena. ¿Puede esta cadena ser la definición completa de un módulo? Parece que no. Me gustaría poder escribir este código que está siendo evaluado de tal manera que tenga múltiples funciones que se llamen entre sí, por ejemplo, un pequeño programa completo, con un punto de entrada predefinido (que podría ser un principal, como como "DynamicModule.handle ([parameter, list])"

Luego está el módulo EEx, que tiene:

compile_string(source, options // [])

Lo cual es genial para hacer plantillas. Pero al final solo parece funcionar para el caso de uso donde hay una cadena y tienes el código Elixir incrustado en él. Evalúa la cadena en el contexto de las opciones y produce una cadena. Estoy buscando compilar la cadena en una o más funciones a las que puedo llamar. (Si solo puedo hacer una función que esté bien, esa función puede coincidir con el patrón o cambiar a hacer las otras cosas que se necesitan ...)

Sé que esto no es convencional, pero tengo mis razones para hacerlo de esta manera y son buenas. Estoy buscando consejos sobre cómo hacer esto, pero no necesito que me digan "no hagas eso". Parece que debería ser posible, Erlang admite la carga de código caliente y Elixir es bastante dinámico, pero no sé la sintaxis o las funciones correctas. Estaré monitoreando esta pregunta de cerca. ¡Gracias por adelantado!

EDITS basados ​​en las primeras respuestas:

Gracias por las respuestas, este es un buen progreso. Como mostró Yuri, eval puede definir un módulo, y como José señala, puedo usar el código eval para partes pequeñas de código con enlaces.

El código que se evalúa (ya sea convertido en módulo o no) va a ser bastante complejo. Y su desarrollo sería lo mejor que implica dividirlo en funciones y llamar a esas funciones.

Para ayudar, permítanme proporcionar algún contexto. Supongamos que estoy construyendo un marco web. El código cargado desde la base de datos es controladores para URI específicos. Entonces, cuando entra una llamada HTTP, podría cargar el código para example.com/blog/. Esta página puede involucrar varias cosas diferentes, como comentarios, una lista de publicaciones recientes, etc.

Dado que muchas personas están llegando a la página al mismo tiempo, estoy generando un proceso para manejar cada vista de página. Por lo tanto, hay muchas ocasiones en que este código puede ser evaluado simultáneamente, para diferentes solicitudes.

La solución del módulo permite dividir el código en funciones para diferentes partes de la página (por ejemplo, la lista de publicaciones, comentarios, etc.) y cargué el módulo una vez, al inicio, y dejé que muchos procesos generaran esa llamada en ello. El módulo es global, ¿correcto?

¿Qué sucede si ya hay un módulo definido? EG: cuando el módulo cambia, y hay procesos que ya llaman a ese módulo.

En iex, puedo redefinir un módulo que ya se ha definido:

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

¿Qué sucede si redefino el módulo en tiempo de ejecución, a todos los procesos que actualmente llaman a ese módulo? Además, ¿funcionará esta redefinición fuera de iex, en funcionamiento normal?

Asumiendo que la redefinición del módulo sería problemática, y que los módulos que son globales podrían tener problemas con las colisiones del espacio de nombres, investigué el uso de eval para definir una función.

Si simplemente puedo hacer que el código de la base de datos defina funciones, entonces esas funciones están dentro del alcance de cualquier proceso, y no tenemos la posibilidad de colisiones globales.

Sin embargo, esto no parece funcionar:

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

También probé:

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

Entonces, en resumen:

  1. ¿Se pueden redefinir los módulos con Code.eval cuando hay procesos invocando en ellos?

  2. ¿Es posible usar Code.eval para hacer funciones cuyo ámbito está vinculado al proceso en el que se invocó Code.eval?

  3. Si entiendes lo que estoy tratando de hacer, ¿puedes sugerir una mejor manera de hacerlo?

Además, si hay un foro mejor en el que debería preguntarle esto, no dude en hacérmelo saber. Y si hay documentos o ejemplos relevantes que debería leer, siéntase libre de señalarme con ellos. No estoy tratando de hacer que hagas todo el trabajo, simplemente no soy capaz de resolverlo por mi cuenta.

Aprendo a Elixir específicamente para la capacidad de evitar el código de forma dinámica, pero mi conocimiento del Elixir es mínimo ahora, recién comencé, y mi erlang también está oxidado.

¡Muchas gracias!


Como describió, hay muchos enfoques diferentes que podría tomar al final se reducen a dos categorías diferentes: 1) compilación de código y 2) evaluación de código. El ejemplo que describió anteriormente requiere compilación, que definirá un módulo y luego deberá invocarlo. Sin embargo, como descubrió, se requiere definir un nombre de módulo y potencialmente purgar y descartar esos módulos cuando la base de datos cambie. Además, tenga en cuenta que la definición de módulos puede agotar la tabla de átomos, ya que se crea un átomo para cada módulo. Solo utilizaría este enfoque si necesita compilar al máximo una docena de módulos.

(Una pequeña nota, Code.compile_string ya define el módulo, por lo que no necesita llamar a load_binary ).

Tal vez un enfoque más simple es llamar a Code.eval pasando el código de la base de datos, por lo tanto, la evaluación del código. Funciona bien si lo único que desea es evaluar algunas reglas personalizadas. Code.eval acepta un enlace, que le permitiría pasar parámetros al código. Supongamos que tiene "a + b" almacenado en la base de datos, donde a y b son los parámetros, puede evaluarlo como:

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

EDITAR EN BASE A EDICIONES DE LA PREGUNTA

Si está evaluando el código, tenga en cuenta que Code.eval devuelve el resultado de evaluar el código y el nuevo enlace, por lo que su ejemplo anterior se escribiría mejor como:

q = "f = function do x, y when x > 0 -> x+y x, y -> x* y end" { _, binding } = Code.eval q binding[:f].(1, 2)

Sin embargo, dado su caso de uso, no consideraría usar eval, pero sí compilaría un módulo. Dado que la información proviene de la base de datos, puede usar este hecho para generar módulos únicos por registro para usted. Por ejemplo, puede suponer que los desarrolladores agregarán los contenidos de un módulo a la base de datos, algo como:

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

Después de obtener esta información de la base de datos, puede compilarla usando:

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

Donde registro es cualquier cosa que obtenga de la base de datos. Suponiendo que el ID de registro es 1 , definiría un módulo FromDB.Data1 que luego podría invocar foo y bar .

En cuanto a la recarga de código, tanto defmodule como Module.create usan :code.load_binary para cargar el módulo. Lo que significa que si actualiza el módulo, la versión anterior se mantendrá hasta que se cargue otra versión nueva.

Una de las cosas que debe agregar también es la caducidad del caché, por lo que no es necesario que compile un módulo en cada solicitud. Esto se puede hacer si tiene una lock_version en la base de datos que incrementa cada vez que cambia el contenido del registro. El código final sería algo así como:

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


Has marcado: Dynamic Compile Library by Jacob Vorreuter . Ver ejemplo a continuación

1> String = "-module(add)./n -export([add/2]). /n add(A,B) -> A + B. /n". "-module(add)./n -export([add/2]). /n add(A,B) -> A + B. /n" 2> dynamic_compile:load_from_string(String). {module,add} 3> add:add(2,5). 7 4> Además, mira esta question y su answer