2013-02-28 11 views
11

Supponiamo che io sono un attore Erlang definita in questo modo:In che modo gli attori di Erlang differiscono dagli oggetti OOP?

counter(Num) -> 
    receive 
    {From, increment} -> 
     From ! {self(), new_value, Num + 1} 
     counter(Num + 1); 
    end.  

E allo stesso modo, ho una classe di Ruby definita in questo modo:

class Counter 
    def initialize(num) 
    @num = num 
    end 

    def increment 
    @num += 1 
    end 
end 

Il codice Erlang è scritto in uno stile funzionale, con la coda ricorsione per mantenere lo stato. Tuttavia, qual è l'impatto significativo di questa differenza? Per i miei occhi ingenui, le interfacce con queste due cose sembrano le stesse: invia un messaggio, lo stato viene aggiornato e ottieni una rappresentazione del nuovo stato.

La programmazione funzionale viene spesso descritta come un paradigma completamente diverso rispetto a OOP. Ma l'attore di Erlang sembra fare esattamente ciò che gli oggetti dovrebbero fare: mantenere lo stato, incapsulare e fornire un'interfaccia basata sui messaggi.

In altre parole, quando sto trasmettendo messaggi tra gli attori di Erlang, come è diverso da quando sto passando messaggi tra oggetti Ruby?

Sospetto che ci siano maggiori conseguenze sulla dicotomia funzionale/OOP di quanto non vedo. Qualcuno può indicarli?

Mettiamo da parte il fatto che l'agente di Erlang sarà programmato dalla VM e quindi può essere eseguito contemporaneamente ad altro codice. Mi rendo conto che questa è una grande differenza tra le versioni di Erlang e Ruby, ma non è quello che sto ottenendo. La concorrenza è possibile in altre lingue, incluso Ruby. E mentre la concorrenza di Erlang può comportarsi in modo molto diverso (a volte meglio), non sto davvero chiedendo le differenze di rendimento.

Piuttosto, sono più interessato al lato funzionale-vs-OOP della domanda.

+0

IMO l'esempio è troppo piccolo/isolato per mostrare differenze significative. Ovviamente in questo caso le differenze * concettuali * sono minori. Altri fattori di considerazione sono più importanti in questo esempio banale. –

risposta

9

In altre parole, quando sto trasmettendo messaggi tra gli attori di Erlang, in che modo è diverso da quando sto passando messaggi tra oggetti Ruby?

La differenza è che in linguaggi tradizionali come Ruby non esiste un messaggio di passaggio ma chiamata di metodo che viene eseguito nello stesso thread, e questo può portare a problemi di sincronizzazione se si dispone di applicazioni multithread. Tutti i thread hanno accesso a ogni altra memoria di thread.

In Erlang tutti gli attori sono indipendenti e l'unico modo per cambiare lo stato di un altro attore è inviare un messaggio.Nessun processo ha accesso allo stato interno di nessun altro processo.

+5

Sì, questa è la differenza principale qui. In Ruby e in altri linguaggi tradizionali chiamano ** il messaggio che passa mentre in Erlang è ** il passaggio dei massaggi. – rvirding

+0

Grazie, Robert! –

0

IMHO questo non è l'esempio migliore per FP vs OOP. Le differenze si manifestano solitamente nell'accedere/iterare e concatenare metodi/funzioni sugli oggetti. Inoltre, probabilmente, la comprensione di ciò che è "stato attuale" funziona meglio in FP.

Qui si mettono due tecnologie molto diverse l'una contro l'altra. Uno capita di essere F, l'altro OO.

La prima differenza che posso individuare subito è l'isolamento della memoria. I messaggi sono serializzati in Erlang, quindi è più facile evitare le condizioni di gara.

Il secondo sono i dettagli di gestione della memoria. In Erlang la gestione dei messaggi è suddivisa in basso tra Sender e Receiver. Ci sono due serie di blocchi della struttura del processo detenute da Erlang VM. Pertanto, mentre Mittente invia il messaggio, acquisisce il blocco che non blocca le operazioni del processo principale (a cui accede tramite il blocco MAIN). Per riassumere, dà a Erlang una natura più soft in tempo reale rispetto a un comportamento totalmente casuale sul lato di Ruby.

0

Guardando dall'esterno, gli attori assomigliano agli oggetti. Incapsulano lo stato e comunicano con il resto del mondo tramite messaggi per manipolare quello stato.

Per vedere come funziona FP, è necessario guardare all'interno di un attore e vedere come si modifica lo stato. Il tuo esempio in cui lo stato è un numero intero è troppo semplice. Non ho il tempo di fornire un esempio completo, ma traccerò il codice. Normalmente, un ciclo attore si presenta come segue:

loop(State) -> 
    Message = receive 
    ... 
    end, 
    NewState = f(State, Message), 
    loop(NewState). 

La differenza più importante da OOP è che non ci sono mutazioni variabili cioè NewState è ottenuto dallo Stato e può condividere la maggior parte dei dati con esso, ma la variabile Stato rimane sempre lo stesso.

Questa è una bella proprietà, dal momento che non abbiamo mai corrotto lo stato attuale. La funzione f di solito esegue una serie di trasformazioni per trasformare lo stato in NewState. E solo se/quando riesce completamente sostituiamo il vecchio stato con quello nuovo chiamando loop (NewState). Quindi l'importante vantaggio è la coerenza del nostro stato.

Il secondo vantaggio che ho trovato è un codice più pulito, ma ci vuole del tempo per abituarsi. Generalmente, dato che non puoi modificare la variabile, dovrai dividere il tuo codice in molte funzioni molto piccole. Questo è davvero carino, perché il tuo codice sarà ben calcolato.

Infine, poiché non è possibile modificare una variabile, è più facile ragionare sul codice. Con gli oggetti mutabili non puoi mai essere sicuro se alcune parti del tuo oggetto saranno modificate e peggiorerà progressivamente se utilizzi variabili globali. Non dovresti incontrare questi problemi quando fai FP.

Per provarlo, è necessario provare a manipolare alcuni dati più complessi in modo funzionale utilizzando strutture di puro erlang (non attori, ets, mnesia o proc dict). In alternativa, puoi provare il rubino con this

0

Erlang include l'approccio di passaggio dei messaggi di OOP di Alan Kay (Smalltalk) e la programmazione funzionale di Lisp.

Quello che descrivi nel tuo esempio è l'approccio del messaggio per OOP. I processi di Erlang che inviano messaggi sono un concetto simile agli oggetti di Alan Kay che inviano messaggi. A proposito, è possibile recuperare questo concetto implementato anche in Scratch dove gli oggetti in esecuzione paralleli inviano messaggi tra di loro.

La programmazione funzionale è la modalità di codifica dei processi. Ad esempio, le variabili in Erlang non possono essere modificate. Una volta che sono stati impostati, puoi solo leggerli. Hai anche una struttura di dati di lista che works è simile agli elenchi Lisp e hai fun che sono ispirati dal lambda di Lisp.

Il messaggio che passa da un lato e il funzionale dall'altro lato sono due cose separate in Erlang. Quando si codificano applicazioni di vita reale, si passa il 98% del tempo a programmare in modo funzionale e il 2% a pensare al passaggio dei messaggi, che viene utilizzato principalmente per la scalabilità e la concorrenza. Per dirlo in un altro modo, quando si arriva ad affrontare complessi problemi di programmazione, probabilmente si utilizzerà il lato FP di Erlang per implementare i dettagli dell'algo, e utilizzare il messaggio che passa per scalabilità, affidabilità, ecc ...

0

Cosa pensi di questo:

thing(0) -> 
    exit(this_is_the_end); 
thing(Val) when is_integer(Val) -> 
    NewVal = receive 
     {From,F,Arg} -> NV = F(Val,Arg), 
         From ! {self(), new_value, NV}, 
         NV; 
     _ -> Val div 2 
    after 10000 
     max(Val-1,0) 
    end, 
    thing(NewVal). 

Quando si spawn il processo, vivrà per la sua propria, diminuendo il suo valore fino a quando non raggiunge il valore 0 e inviare il messaggio { 'EXIT', this_is_the_end } a qualsiasi processo legato ad esso, a meno che non si prende cura di eseguire qualcosa di simile:

ThingPid ! {self(),fun(X,_) -> X+1 end,[]}. 
% which will increment the counter 

o

ThingPid ! {self(),fun(X,X) -> 0; (X,_) -> X end,10}. 
% which will do nothing, unless the internal value = 10 and in this case will go directly to 0 and exit 

In questo caso puoi vedere che l '"oggetto" vive la propria vita da solo in parallelo con il resto dell'applicazione, che può interagire con l'esterno quasi senza alcun codice, e che l'esterno può chiedergli di fare cose che tu non sapevo quando hai scritto e compilato il codice.

Si tratta di un codice di stupido, ma ci sono alcuni principi che sono utilizzati per implementare applicazioni come transazione mnesia, i comportamenti ... IMHO il concetto è veramente diverso, ma bisogna provare a pensare diverso se si desidera utilizzare correttamente. Sono abbastanza sicuro che sia possibile scrivere codice "OOPlike" in Erlang, ma sarà estremamente difficile evitare la concorrenza: o), e alla fine nessun vantaggio. Dai un'occhiata al principio OTP che fornisce alcune tracce sull'architettura dell'applicazione in Erlang (alberi di supervisione, pool di "1 server client singoli", processi collegati, processi monitorati e, naturalmente, pattern che corrispondono a singola assegnazione, messaggi, cluster di nodi ...).

Problemi correlati