Подтвердить что ты не робот

Как вы динамически создаете и загружаете модули во время выполнения в Elixir или Erlang?

Основной сценарий таков: мне нужно загрузить текст из базы данных, а затем превратить этот текст в модуль Elixir (или модуль Erlang), а затем выполнить вызовы. Текст фактически такой же, как файл модуля. Таким образом, это форма загрузки горячего кода. Я хочу скомпилировать "файл", а затем загрузить полученный модуль, а затем выполнить вызовы. Позже я выгружу его. Единственное различие заключается в том, что код существует в базе данных, а не на диске. (и он не существует в то время, когда я пишу код, который будет загружать его.)

Я знаю, что Erlang поддерживает загрузку горячего кода, но, похоже, сосредоточен на компиляции файлов на диске и загрузке балок. Я хочу сделать это как более динамичный процесс, и я не буду заменять исполняемый код, но загружаю код, затем запускаю его, а затем выгружаю.

В Elixir есть несколько объектов для оценки кода во время выполнения. Я пытаюсь понять, как это сделать с ними, и документация немного разрежена.

Code.compile_string(string, "nofile")

"возвращает список кортежей, где первый элемент является именем модуля, а второй - его двоичным". Итак, теперь у меня есть имена модулей и их двоичные файлы, но я не знаю, как загружать двоичные файлы во время выполнения и вызывать их. Как мне это сделать? (Нет никакой функции для этого в библиотеке кода, которую я вижу.)

Возможно, я мог бы использовать функцию Erlang:

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

Итак, это возвращает кортеж с атомарным "модулем", а затем с модулем. Если строка, загруженная из базы данных, определила модуль под названием "Париж", как в моем коде я бы выполнил

paris.handler([parameters])

так как я не знаю заранее, что будет модуль под названием paris? Я мог бы знать, что строка "paris" также хранится в базе данных, что это имя, но есть ли способ вызова в модуль, используя строку в качестве имени модуля, который вы вызываете?

Существует также:

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

Что оценивает содержимое строки. Может ли эта строка быть полным определением модуля? Кажется, нет. Я хотел бы иметь возможность написать этот код, который оценивается таким образом, что он имеет несколько функций, которые называют друг друга - например. полная небольшая программа с заранее определенной точкой входа (которая может быть основной, такой как "DynamicModule.handle([параметр, список])"

Тогда существует модуль EEx, который имеет:

compile_string(source, options // [])

Это отлично подходит для создания шаблонов. Но в конечном итоге это, похоже, работает только в случае использования, где есть строка, и у вас есть код Elixir, встроенный в него. Он оценивает строку в контексте параметров и создает строку. Я пытаюсь скомпилировать строку в одну или несколько функций, которые я могу вызвать. (Если я могу только сделать одну функцию прекрасной, эта функция может сопоставить шаблон или переключиться на выполнение других необходимых вещей....)

Я знаю, что это нетрадиционно, но у меня есть причины для этого, и они хорошие. Я ищу советы о том, как это сделать, но не нужно говорить "не делай этого". Похоже, что это должно быть возможно, Erlang поддерживает загрузку горячего кода, а Elixir довольно динамичен, но я просто не знаю синтаксиса или правильных функций. Я буду внимательно следить за этим вопросом. Спасибо заранее!


РЕДАКТИРОВАТЬ на основе первых ответов:

Спасибо за ответы, это хороший прогресс. Как показал Юрий, eval может определить модуль, и, как указывает Хосе, я могу просто использовать код eval для небольших частей кода с привязками.

Оцененный код (независимо от того, превращен ли он в модуль или нет) будет довольно сложным. И его развитие было бы лучше всего сбить его на функции и вызвать эти функции.

Чтобы помочь, позвольте мне указать некоторый контекст. Предположим, что я создаю веб-структуру. Код, загруженный из базы данных, является обработчиком для определенных 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 тоже ржавый.

Большое спасибо!

4b9b3361

Ответ 1

Как вы описали, есть много разных подходов, которые вы можете использовать, в конечном итоге они сводятся к двум различным категориям: 1) компиляция кода и 2) оценка кода. Пример, который вы описали выше, требует компиляции, которая определит модуль, а затем вам придется его вызывать. Однако, как вы узнали, для этого требуется определить имя модуля и, возможно, очистить и отбросить эти модули при изменении базы данных. Также обратите внимание, что определяющие модули могут исчерпать таблицу атомов, поскольку атом создается для каждого модуля. Я бы использовал этот подход, только если вам нужно скомпилировать не более десятка модулей.

(Небольшая заметка, Code.compile_string уже определяет модуль, поэтому вам не нужно вызывать load_binary).

Возможно, более простой подход - вызвать Code.eval передавая код из базы данных, таким образом, оценивая код. Он отлично работает, если все, что вам нужно, это оценить некоторые пользовательские правила. Code.eval принимает привязку, которая позволит вам передавать параметры в код. Предположим, у вас есть "a + b", хранящийся в базе данных, где a и b - параметры, вы можете оценить это как:

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

Где запись - это все, что вы получаете из базы данных. Предполагая, что идентификатор записи равен 1, он будет определять модуль FromDB.Data1 который затем вы сможете вызывать foo и bar.

Что касается перезагрузки кода, и defmodule и Module.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

Ответ 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

Помогает ли это?

Ответ 3

Вы проверили: Dynamic Compile Library by Jacob Vorreuter. Пример ниже

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>
Кроме того, ознакомьтесь с этим вопросом и его .