Como você cria e carrega módulos dinamicamente em tempo de execução no Elixir ou Erlang?

O cenário básico é o seguinte: eu preciso carregar o texto de um banco de dados e, em seguida, transformar esse texto em um módulo Elixir (ou um módulo Erlang) e, em seguida, fazer chamadas para ele. O texto é efetivamente o mesmo que um arquivo de módulo. Portanto, esta é uma forma de carregamento de código ativo. Eu quero compilar o "arquivo" e, em seguida, carregar o módulo resultante, em seguida, fazer chamadas para ele. Mais tarde eu vou descarregar. A única diferença é que o código existe em um banco de dados em vez de um arquivo no disco. (e não existe no momento em que estou escrevendo o código que irá carregá-lo.)

Eu sei que o Erlang suporta o carregamento de código ativo, mas parece focado em compilar arquivos no disco e depois carregar os feixes. Eu gostaria de fazer isso como um processo mais dinâmico e não substituirei o código em execução, mas sim o carregamento do código, depois executá-lo e depois descarregá-lo.

Existem várias facilidades no Elixir para avaliar o código em tempo de execução. Eu estou tentando descobrir como fazer isso com eles, e a documentação é um pouco escassa.

Code.compile_string(string, "nofile")

"retorna uma lista de tuplas onde o primeiro elemento é o nome do módulo eo segundo é seu binário". Então, agora eu tenho os nomes dos módulos e seus binários, mas eu não sei de uma maneira de, em seguida, carregar os binários no tempo de execução e chamá-los. Como eu faria isso? (Não há nenhuma função para isso na biblioteca de código que eu possa ver.)

Possivelmente eu poderia então usar a função Erlang:

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

Ok, isso retorna uma tupla com o "módulo" do átomo e, em seguida, o módulo. Se a string carregada do banco de dados definisse um módulo chamado "Paris", como no meu código eu executaria

paris.handler([parameters])

já que não sei de antemão que haverá um módulo chamado paris? Eu poderia saber, por ter a string "paris" também armazenada no banco de dados que este é o nome, mas existe alguma maneira de chamar um módulo, usando uma string como o nome do módulo que você está chamando?

Há também:

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

Qual avalia o conteúdo da string. Esta string pode ser toda a definição de um módulo? Não parece. Gostaria de poder escrever este código que está sendo avaliado de forma que ele tenha várias funções que se ligam - por exemplo, um pequeno programa completo, com um ponto de entrada pré-definido (que poderia ser um main, como "DynamicModule.handle ([parameter, list])"

Então há o módulo EEx, que tem:

compile_string(source, options // [])

O que é ótimo para fazer modelos. Mas, no final, parece funcionar apenas para o caso de uso em que há uma string e você tem o código Elixir embutido nela. Avalia a string no contexto das opções e produz uma string. Eu estou procurando compilar a string em uma ou mais funções que eu posso chamar. (Se eu puder fazer apenas uma função, tudo bem, essa função pode combinar com padrões ou mudar para outras coisas que são necessárias ...)

Eu sei que isso não é convencional, mas eu tenho minhas razões para fazer assim e elas são boas. Estou procurando conselhos sobre como fazer isso, mas não precisa ser dito "não faça isso". Parece que deveria ser possível, o Erlang suporta o carregamento de código ativo e o Elixir é bastante dinâmico, mas eu simplesmente não conheço a sintaxe, ou as funções certas. Eu vou estar acompanhando esta questão de perto. Desde já, obrigado!

EDITOS com base nas primeiras respostas:

Obrigado pelas respostas, isso é um bom progresso. Como Yuri mostrou, eval pode definir um módulo, e como José aponta, eu posso apenas usar o código eval para pequenas partes do código com ligações.

O código que está sendo avaliado (seja ele transformado em um módulo ou não) será bastante complexo. E o seu desenvolvimento seria melhor, dividindo-se em funções e chamando essas funções.

Para ajudar, deixe-me fornecer algum contexto. Suponho que estou construindo um framework web. O código carregado a partir do banco de dados é manipuladores para URIs específicos. Então, quando uma chamada HTTP entra, eu posso carregar o código para example.com/blog/ Esta página pode envolver várias coisas diferentes, como comentários, uma lista de posts recentes, etc.

Como muitas pessoas estão acessando a página ao mesmo tempo, estou gerando um processo para lidar com cada exibição de página. Assim, muitas vezes esse código pode ser avaliado simultaneamente, para diferentes solicitações.

A solução do módulo permite dividir o código em funções para diferentes partes da página (por exemplo: a lista de posts, comentários, etc.) E eu carregaria o módulo uma vez, na inicialização, e deixaria muitos processos gerarem essa chamada afim disso. O módulo é global, correto?

O que acontece se houver um módulo já definido? EG: Quando o módulo muda, e já há processos chamando esse módulo.

No iex, eu posso redefinir um módulo que já foi definido:

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

O que acontece se eu redefinir o módulo em tempo de execução, para todos os processos atualmente chamando para esse módulo? Além disso, esta redefinição funcionará fora do iex, em operação normal?

Assumindo que a redefinição do módulo seria problemática e que os módulos que são globais podem ter problemas com colisões de namespace, procurei usar eval para definir uma função.

Se eu puder simplesmente ter o código do banco de dados definido funções, então essas funções estão dentro do escopo de qualquer processo, e não temos a possibilidade de colisões globais.

No entanto, isso não 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

Eu também tentei:

    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

Então, em resumo:

Os módulos podem ser redefinidos usando Code.eval quando há processos chamando-os?

É possível usar Code.eval para criar funções cujo escopo está vinculado ao processo no qual Code.eval foi chamado?

Se você entende o que estou tentando fazer, você pode sugerir uma maneira melhor de fazer isso?

Além disso, se houver um fórum melhor onde eu deveria estar perguntando isso, fique à vontade para me avisar. E se houver documentos ou exemplos relevantes que eu deva ler, sinta-se à vontade para me indicar. Eu não estou tentando fazer você fazer todo o trabalho, eu simplesmente não consigo entender sozinha.

Eu estou aprendendo Elixir especificamente para a capacidade de avaliar dinamicamente o código, mas o meu conhecimento Elixir é mínimo agora, eu apenas comecei e meu erlang está enferrujado também.

Muito obrigado!

questionAnswers(3)

yourAnswerToTheQuestion