Andando per Erlang qui.
A livello base, sono disponibili due opzioni. Se si sta solo che vogliono utilizzare become
per modificare il comportamento di un determinato processo (vedi punto 2 della lista al punto 2.1.3), quindi è solo una questione di chiamare il ciclo successivo con una diversa funzione ricorsiva:
loop(State) ->
receive
{normal, Msg} -> loop(State);
{change, NewLoop} -> NewLoop(State)
end.
Supponendo NewLoop
è una funzione di ordine superiore, ogni volta che si invia il messaggio {change, NewLoop}
a un processo in esecuzione inizialmente la funzione loop/1
, sarà quindi utilizzare NewLoop
come la sua definizione.
La seconda opzione è quella in cui si desidera che il processo funga da proxy (e modifica il comportamento). Questo è simile a quello suggerito da Marcelo Cantos. Basta avere il ciclo di processo e di inoltrare messaggi ad uno nuovo (rubare il suo codice):
become(Pid) ->
receive
Msg -> Pid ! Msg
end,
become(Pid).
In teoria, questo fa quello che il giornale avrebbe chiesto. In pratica, però, ci sono dei rischi nell'usare la seconda opzione in un sistema Erlang reale. Nelle comunicazioni tra due processi, è un concetto frequente di "timbrare" il messaggio con l'identificatore del processo del mittente e che la risposta verrà contrassegnata con l'identificativo del processo del ricevente. Uno scambio di si potrebbero fare i seguenti messaggi (non si tratta di codice, solo una notazione a mano):
A = <0.42.0> <-- process identifier
B = <0.54.0>,
A: {A, "hello"},
B: {B, "hi"},
A: {A, "how are you?"}.
B: {B, "Fine!"}.
Così, quando A
si aspetta un messaggio da B
, sarà in grado di eguagliare solo per questi, utilizzando un modello ad esempio {B, Message}
. Nel caso di un messaggio inoltrato, questo schema di indirizzamento diventa non valido e semplicemente rotto.
Un'alternativa sarebbe utilizzare i riferimenti (make_ref()
) come schema di indirizzamento per abbinare i messaggi sopra i Pid di ritorno. Ciò separerebbe l'uso del Pid come indirizzo e identificatore usando due entità diverse.
C'è un altro problema, anche se l'indirizzamento è sicuro: dipendenze di processo. Cosa succede per i processi denominati, i processi che si bloccano, i monitor, ecc.? I processi client potrebbero avere monitor, collegamenti e quant'altro impostato per assicurarsi che nulla vada storto senza essere avvisati. Modificando il processo di routing per i segnali di uscita trappola e li trasmette, dovrebbe essere possibile per rendere le cose più sicuro:
loop(State) ->
receive
{normal, Msg} -> loop(State);
{become, Pid} ->
process_flag(trap_exit, true), % catch exit signals as messages
link(Pid), % make it so if one process crashes, the other receives the signal
become(Pid)
end.
become(Pid) ->
receive
{'EXIT', Pid, killed} -> exit(self(), kill); %% uncatchable termination
{'EXIT', Pid, normal} -> ok; %% die normally too
{'EXIT', Pid, Reason} -> exit(Reason); %% die for the same reason as Pid
Msg -> Pid ! Msg %% forward the message
end,
become(Pid).
Questa testato codice dovrebbe essere più sicuro in quanto i processi a seconda del primo processo otterrà gli stessi messaggi di errore come quello rappresentato da Pid
in become(Pid)
, rendendo il routing piuttosto trasparente. Tuttavia, non darei alcuna garanzia che ciò possa funzionare a lungo termine con applicazioni reali.
Anche se è possibile e abbastanza concettualmente semplice da rappresentare e fare cose come become
, la libreria standard di Erlang non è stata pensata con il secondo caso d'uso in mente.Per le applicazioni del mondo reale, posso solo raccomandare il primo metodo, che è ampiamente utilizzato da ogni applicazione Erlang che esiste al momento. La seconda è raro e potrebbe causare problemi
* * Le chiamate alla funzione become/1
nell'ultimo esempio dovrebbe probabilmente ?MODULE:become(Pid)
per evitare potenziali incidenti relativi a codice carico caldo in futuro. *
La semantica della composizione delle funzioni sembra incoerente con le altre descrizioni di * diventa * in questa pagina, che ho letto come dicendo che la semantica di un attore sostituisce l'altra, non la aggiunge. –
OK, quindi assegna il nuovo attore alla stessa variabile. – Apocalisp