2012-11-04 12 views
30

Lo scenario di base è questo: ho bisogno di caricare il testo da un database, e quindi trasformare quel testo in un modulo Elixir (o un modulo di Erlang) e quindi effettuare chiamate in esso. Il testo è effettivamente lo stesso di un file modulo. Quindi questa è una forma di caricamento di hot code. Voglio compilare il "file" e quindi caricare il modulo risultante, quindi effettuare chiamate in esso. Più tardi lo scaricherò. L'unica differenza è che il codice esiste in un database anziché in un file sul disco. (e non esiste al momento in cui sto scrivendo il codice che verrà caricato.)Come si creano e si caricano i moduli dinamicamente in fase di runtime in Elixir o Erlang?

So che Erlang supporta il caricamento di codice caldo, ma sembra concentrato sulla compilazione di file su disco e quindi sul caricamento dei raggi. Desidero farlo come un processo più dinamico e non sostituirò il codice in esecuzione, ma caricando il codice, eseguendolo, quindi scaricandolo.

Ci sono diversi servizi in Elixir per la valutazione del codice in fase di esecuzione. Sto cercando di capire come farlo con loro, e la documentazione è un po 'scarsa.

Code.compile_string(string, "nofile") 

"restituisce una lista di tuple in cui il primo elemento è il nome del modulo e il secondo è il suo binario". Quindi, ora ho i nomi dei moduli e i loro binari, ma non conosco un modo per poi caricare i binari nel runtime e chiamarli. Come potrei farlo? (Non c'è alcuna funzione per questo nella libreria di codice che posso vedere.)

Forse ho potuto quindi utilizzare la funzione di Erlang:

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

Ok, quindi questo restituisce una tupla con il "modulo" dell'atomo e quindi il modulo. Se la stringa caricata dal database definito un modulo chiamato "Parigi", come nel mio codice dovrei quindi eseguire

paris.handler([parameters]) 

dato che non so in anticipo che ci sarà un modulo chiamato Paris? Potrei sapere, avendo la stringa "paris" anche memorizzata nel database che questo è il nome, ma c'è un modo per chiamare in un modulo, usando una stringa come nome del modulo che stai chiamando?

C'è anche:

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

che valuta il contenuto della stringa. Questa stringa può essere l'intera definizione di un modulo? Non sembra. Mi piacerebbe essere in grado di scrivere questo codice che viene valutato in modo tale da avere più funzioni che si chiamano a vicenda - ad es. un piccolo programma completo, con un punto di ingresso predefinito (che potrebbe essere un principale, come ad esempio "DynamicModule.handle ([parametro, l'elenco])"

Poi c'è il modulo EEx, che ha:

compile_string(source, options // []) 

Che è ottimo per fare modelli, ma alla fine sembra funzionare solo per il caso d'uso in cui c'è una stringa e in esso è incorporato il codice Elixir. Valuta la stringa nel contesto delle opzioni e produce una stringa Sto cercando di compilare la stringa in una o più funzioni che posso quindi chiamare. (Se posso solo fare una funzione che va bene, quella funzione può pattern match o passare a fare le altre cose che sono necessarie ....)

So che questo non è convenzionale, ma ho le mie ragioni per farlo in questo modo e sono buoni. Sto cercando consigli su come farlo, ma non c'è bisogno di sentirmi dire "non farlo". Sembra che dovrebbe essere possibile, Erlang supporta il caricamento di hot code e Elixir è piuttosto dinamico, ma io non conosco la sintassi o le funzioni giuste.Seguirò da vicino questa domanda. Grazie in anticipo!


EDITS sulla base delle prime risposte:

Grazie per le risposte, questo è un buon progresso. Come ha mostrato Yuri, eval può definire un modulo, e come sottolinea José, posso semplicemente usare la valutazione del codice per piccole parti di codice con associazioni.

Il codice che viene valutato (trasformato in modulo o meno) sarà piuttosto complesso. E il suo sviluppo sarebbe meglio coinvolgere scomposizione in funzioni e chiamando quelle funzioni.

Per aiutare, lasciatemi fornire un contesto. Supponiamo che sto costruendo un framework web. Il codice caricato dal database è gestori per URI specifici. Quindi, quando arriva una chiamata HTTP, potrei caricare il codice per esempio.com/blog/ Questa pagina potrebbe comportare diverse cose, come commenti, un elenco di post recenti, ecc.

Poiché molte persone stanno colpendo la pagina allo stesso tempo, sto generando un processo per gestire ogni visualizzazione di pagina. Quindi ci sono molte volte in cui questo codice può essere valutato simultaneamente, per richieste diverse.

La soluzione del modulo consente di suddividere il codice in funzioni per diverse parti della pagina (ad esempio: l'elenco di post, commenti, ecc.) E vorrei caricare il modulo una volta, all'avvio, e lasciare molti processi generano quella chiamata in esso. Il modulo è globale, corretto?

Cosa succede se c'è già un modulo definito? EG: Quando il modulo cambia, e ci sono processi che già chiamano quel modulo.

In iex, sono in grado di ridefinire un modulo che è già stato definito:

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

Cosa succede se ridefinire il modulo in fase di esecuzione, a tutti i processi attualmente rimettere in quel modulo? Inoltre, questa ridefinizione funzionerà al di fuori di iex, nel normale funzionamento?

Supponendo che la ridefinizione del modulo sarebbe problematica e che i moduli che sono globali potrebbero incontrare problemi con le collisioni nello spazio dei nomi, ho cercato di utilizzare eval per definire una funzione.

Se posso semplicemente fare in modo che il codice dal database definisca le funzioni, allora tali funzioni rientrano nell'ambito di qualsiasi processo e non abbiamo la possibilità di collisioni globali.

Tuttavia, questo non sembra funzionare:

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 

Ho anche provato:

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 

Così, in sintesi:

  1. può essere ridefinito utilizzando i moduli di codice .eval quando ci sono processi che chiamano in loro?

  2. È possibile utilizzare Code.eval per creare funzioni il cui ambito è associato al processo in cui è stato chiamato Code.eval?

  3. Se capisci cosa sto cercando di fare, puoi suggerire un modo migliore per farlo?

Inoltre, se c'è un forum migliore in cui dovrei chiedere questo, non esitate a farmelo sapere. E se ci sono documenti o esempi pertinenti che dovrei leggere, per favore sentitevi liberi di indicarmi. Non sto cercando di farti fare tutto il lavoro, non riesco proprio a capirlo da solo.

Sto imparando l'elisir appositamente per l'abilità di svelare dinamicamente il codice, ma la mia conoscenza dell'elisir è minima ora- ho appena iniziato- e anche il mio erlang è arrugginito.

Grazie mille!

risposta

26

Come lei ha descritto, ci sono molti approcci diversi si poteva prendere dalla fine si riducono a due diverse categorie: 1) il codice di compilazione e 2) valutazione del codice. L'esempio che hai descritto sopra richiede la compilazione, che definirà un modulo e quindi dovresti invocarlo. Tuttavia, come hai scoperto, è necessario definire un nome di modulo e potenzialmente eliminare e scartare quei moduli quando il database cambia. Inoltre, si noti che la definizione dei moduli può esaurire la tabella atom, poiché viene creato un atomo per ogni modulo. Userò questo approccio solo se hai bisogno di compilare al massimo una dozzina di moduli.

(Una piccola nota, Code.compile_string definisce già il modulo, quindi non è necessario chiamare load_binary).

Forse un approccio più semplice è chiamare Code.eval passando il codice dal database, quindi la valutazione del codice. Funziona bene se tutto ciò che vuoi è valutare alcune regole personalizzate. Code.eval accetta un'associazione che consente di passare parametri al codice. Supponiamo di avere "a + b" memorizzate nel database, dove a e b sono i parametri, si potrebbe valutare come:

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

EDIT BASATI SU DOMANDA DI EDITS

Se si sta valutando codice, tenere presente che Code.eval restituisce il risultato della valutazione del codice e la nuova associazione, così il vostro esempio di cui sopra sarebbe meglio scritto come:

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

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

Tuttavia, dato il tuo caso d'uso, non prenderei in considerazione l'utilizzo di eval, ma vorrei davvero compilare un modulo. Poiché le informazioni provengono dal database, puoi utilizzare questo fatto per generare moduli unici per record per te.Ad esempio, si può assumere gli sviluppatori aggiungeranno il contenuto di un modulo per il database, qualcosa come:

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

Dopo aver ottenuto queste informazioni dal database, è possibile compilare utilizzando:

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 

Dove record è tutto ciò che si ottiene dal database. Supponendo che l'ID del record sia 1, definirebbe un modulo FromDB.Data1 che quindi sarebbe possibile invocare foo e bar.

Per quanto riguarda il caricamento del codice, sia defmodule sia Module.create utilizzare :code.load_binary per caricare il modulo. Ciò significa che se si aggiorna il modulo, la vecchia versione viene mantenuta fino a quando non viene caricata un'altra nuova versione.

Una delle cose da aggiungere è la scadenza della cache, quindi non è necessario compilare un modulo su ogni richiesta. Questo può essere fatto se si ha una lock_version nel database che si incrementa ogni volta che si modifica il contenuto del record. Il codice finale sarebbe qualcosa del tipo:

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 
+0

Ciò imporrebbe una forma predeterminata sul codice, in modo tale che sia suddivisa in piccole stringhe per la valutazione. Ho modificato la mia domanda per spiegare ulteriormente, ma se potessi definire le funzioni all'interno del codice e poi chiamarle all'interno della stringa, e anche dall'esterno, sarebbe probabilmente l'ideale. Ma non ha funzionato quando l'ho provato. Mi piacerebbe che STRING fosse in grado di definire le funzioni A, B e C, ad esempio, e il codice esterno definisca D, E e F. E dall'interno della stringa inviata a Code.eval e all'esterno, essere in grado di chiamare A, B, C, D, E & F. EG: tutte le funzioni sono mirate al processo. È possibile? – nirvana

+0

Ho appena risposto alle tue nuove domande. –

+0

Solo un altro po 'di confusione. Guardando il tuo codice, l'ultima riga è semplicemente "modulo" che è effettivamente "FromDB.Data1" in questo esempio. Quando si ha "modulo" su una linea in questo modo, è l'equivalente di chiamare FromDB.Data1.main() o qualche punto di ingresso predefinito? In altre parole, il codice crea un modulo il cui nome è basato sulle informazioni del database, il che va bene, come faccio a chiamarlo dal codice che è al di fuori di esso? Posso scrivere "module.renderRequest (a, b, c)" e chiamerà la funzione renderRequest definita nel modulo caricato dal database? – nirvana

9

Code.eval può essere utilizzato per definire un modulo:

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 

fa questo aiuto?

+0

Questo ha aiutato molto e ha portato a una notte produttiva di esplorazione. Ho votato la tua risposta, ma sfortunatamente qualche idiota l'ha svalutato. Mi dispiace che ci sia solo un upvote che posso dare. Dal momento che la mia domanda era un po 'prolissa e verbosa, l'ho estesa sulla base delle tue risposte con i risultati dei miei esperimenti: puoi ridefinire i moduli, e questo è positivo, ma non sono sicuro delle conseguenze. E non riesco a definire semplici funzioni (che presumibilmente avrebbero lo scopo del processo in cui è stato chiamato Code.eval). – nirvana

2

Avete controllato: Dynamic Compile Library by Jacob Vorreuter. Vedere l'esempio di seguito

 
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> 
Inoltre, controlla questo question e la sua answer

+0

Quella libreria è utile e mostra che ciò che devo fare può essere fatto in erlang, che sarà molto utile se decido che non voglio programmare in elisir. Tuttavia, la mia preoccupazione per i moduli è la loro natura globale. Considera l'esempio a cui ti riferisci- immagina che il server abbia due richieste per farlo funzionare. Forse controlla se il modulo è già caricato e non lo carica in modo dinamico due volte ... ma quando viene eseguito, come può sapere che è sicuro scaricarlo? Che non ci sono altri processi che usano quel modulo? Penso che l'ambito del processo sarebbe l'ideale. – nirvana

+1

penso che in tal caso, si proverebbe a ottenere informazioni sul modulo per vedere se è già caricato, quindi l'applicazione dovrebbe agire di conseguenza. –

Problemi correlati