Erlang/OTP

Applicazioni Parallele, Distribuite, Real-Time & Mission-Critical

Nota: Questo articolo è stato scritto e pubblicato più di dieci anni fa, ma ora lo pubblico di nuovo, praticamente senza nessuna modifica, visto che lo trovo oggi ancora molto attuale.

Il linguaggio di programmazione Erlang non è affatto nuovo e non avrebbe nemmeno destato la minima attenzione da parte dei programmatori se non si fossero presentati simultaneamente due fenomeni che condizioneranno per molto tempo il futuro dell'informatica. Il primo fenomeno è legato all'hardware e consiste nella progressiva diffusione di computer con più processori, mentre il secondo è legato al software e precisamente alle applicazioni web della prossima generazione. Quindi si stanno aprendo nuovi fronti e rinnovate opportunità nella società dell'informazione grazie a queste due fenomeni.

Erlang, nato più di vent'anni fa nei laboratori di una società di telecomunicazioni, la Ericsson per la precisione, è un linguaggio appositamente progettato e realizzato per scrivere applicazioni di telecomunicazioni per assolvere compiti specifici. Cioè, software per gestire router, switch, centraline telefoniche e servizi di telecomunicazioni. L'obiettivo finale è quello di disporre di un software in grado di funzionare per anni senza interruzioni in ottimali condizioni di sicurezza. Per loro natura, la maggior parte delle applicazioni di telecomunicazioni sono parallele, distribuite, real-time e mission-critical.

Oggi, i fatti dicono che tali obiettivi sono stati pienamente raggiunti grazie al linguaggio Erlang. Ma cosa c'entrano i computer con più processori, le applicazioni web di nuova generazione con Erlang?
La risposta sta nella sorprendente somiglianza tra le applicazioni di telecomunicazioni e quelle web. Quindi, grazie ad Erlang è possibile scrivere applicazioni web con qualità del tutto simili a quelle di telecomunicazioni. Ossia, applicazioni parallele, distribuite, real-time e mission-critical.
Un'applicazione Erlang considera un computer come un nodo che comunica con altri nodi e questa è una nuova visione di concepire e sviluppare il software.

Piattaforma

Parlare di Erlang senza parlare di OTP (Open Telecom Platform) sarebbe come parlare del Colosseo senza parlare dell'Antica Roma. In effetti Erlang è solo il linguaggio, mentre la OTP è la vastissima libreria, interamente scritta in Erlang, che mette a disposizione dello sviluppatore tutto quello che serve per scrivere applicazioni parallele, distribuite, real-time e mission-critical anche molto complesse. In questo articolo introduttivo, ci limitiamo solo a presentare il linguaggio Erlang ed alcune sue peculiarità.

Il linguaggio Erlang

Erlang è un linguaggio logico-funzionale con una sintassi simile al Prolog (il linguaggio di intelligenza artificiale). È a singola assegnazione delle variabili ed utilizza la ricursione per l'esecuzione dei cicli. Le sue maggiori peculiarità sono le seguenti:

  1. Erlang è un linguaggio interpretato con gestione automatica della memoria, dinamico e parallelo dove ciascuna applicazione gira come processo nel proprio spazio di memoria. I processi Erlang sono isolati, estremamente leggeri ed efficienti, non condividono dati ed il loro numero è illimitato.
  2. Le applicazioni Erlang sono parallele e possono girare in ambienti distribuiti dove tutti i nodi sono visti come macchine virtuali Erlang. Si tratta di qualcosa di simile a Java ma di gran lungo più coerente ed efficiente.
  3. Erlang è un linguaggio robusto perché esegue tutti i controlli necessari sul nodo prima di procedere con l'inizializzazione di un processo.
  4. Erlang è stato concepito per lo sviluppo di applicazioni real-time di telecomunicazioni dove, a volte, è importante ottenere una risposta entro qualche millisecondo.
  5. È possibile in Erlang aggiornare il software a caldo senza la necessità di fermare l'applicazione che lo utilizza.
  6. Il codice può essere caricato in modo incrementale ed eseguito osservando ed analizzando il suo comportamento con molta trasparenza.
  7. È possibile far comunicare un'applicazione Erlang con altre applicazioni scritte in altri linguaggi e che girano su computer remoti.
C'è anche da aggiungere che Erlang/OTP è multipiattaforma ed è freeware ed open source; caratteristiche molto interessanti per chi vuole provare il linguaggio senza spendere nulla.

Programmazione dichiarativa e funzionale

Per chi proviene dai tradizionali linguaggi di programmazione, tipo il C/C++, il Pascal/Object Pascal o Java, l'impatto con Erlang sarà in qualche modo traumatico. La sintassi apparentemente contorta, la totale mancanza di costrutti come " for .. to", "while .. do", non ci sono variabili globali, non si può assegnare più di un valore ad una stessa variabile... Per chi invece proviene da qualsiasi altro linguaggio funzionale o dichiarativo (dal Prolog in modo particolare) si troverebbe direttamente in casa.

Ecco qualche esempio:
X=5 (ok)
X=3 (no, X ormai vale solo 5)
X=X+1 (no)

Immaginate, per esempio, quanto sia apparentemente frustrante e complicato scrivere un'applicazione scientifica in questo linguaggio visto che i cicli e l'assegnazione di diversi valori alla stessa variabile è alla base della programmazione scientifica.

Comunque, una volta superata la soglia di confidenza con lo stile di programmazione funzionale, programmare in Erlang diventa molto semplice, naturale e addirittura divertente.

Il foglio elettronico è una tipica applicazione che utilizza il paradigma funzionale nell'elaborazione dei calcoli. In effetti, scrivere per esempio nella casella A3 "=A1 + A2", significa che abbiamo chiesto di sommare il contenuto della casella A1 alla casella A2 e metterlo in A3. Ma il valore di A1 e di A2 potrebbe essere di tipo numerico, una formula, un riferimento ad altre caselle, oppure una combinazione di tutte queste cose messe insieme. Questo è il principio della programmazione funzionale che conferisce alle applicazioni una grande flessibilità e naturalezza.

È un modo del tutto diverso dalla programmazione ad oggetti, dove si tende a mescolare dati e funzioni in un unico contenitore (oggetto). Dati e funzioni non sono delle stesso tipo ed è molto logico tenerli separati. Perciò, sono in molti a considerare che, in linea generale, la programmazione ad oggetti abbia portato maggiore confusione e solo pochi benefici al mondo dell'informatica. Cioè, non è un male programmare ad oggetti, ma lo diventa quando è l'unico modo per scrivere del software. In molti casi un semplice approccio procedurale, o funzionale, è molto più conveniente.

La validità di Erlang per lo sviluppo di alcuni tipi di applicazioni è attestata da un fatto incontrastabile. È servito alla Ericsson e ad altre società di telecomunicazioni per sviluppare oggi le più complesse applicazioni distribuite e perfettamente funzionanti come da progetto. Sarebbe stato semplicemente quasi impossibile svilupparle in C++ o in Java. Il C++ non è affatto adatto per lo sviluppo di applicazioni distribuite; Java sarebbe collassato a meno di un decimo di strada. Ada è sicuramente all'altezza della situazione, ma i costi sarebbero stati eccessivamente elevati.

Programmare in Erlang

Per iniziare a programmare in Erlang è necessario solo installare la piattaforma Erlang/OTP disponibile presso il sito http://www.erlang.org/ sul vostro computer. L'installazione su Windows consiste in un doppio click sull'eseguibile (otp_win32_R12B-0.exe) per avere in una paio di minuti tutto installato: interprete e librerie varie, una miriade di esempi e manuali nel formato HTML.

Per quanto riguarda l'installazione su Linux, conviene compilare la piattaforma da codice sorgente. I tempi di compilazione dipendono dalla velocità del vostro processore e dalla quantità di memoria disponibile ma non dovrebbe presentare nessun problema anche su un vecchio PC poco dotato come il mio. Infatti, sono più di cinque anni che compilo ogni nuova versione della piattaforma Erlang/OTP da codice sorgente senza riscontrare il minimo problema. Il tutto è molto chiaro e ben spiegata nella file README. Un programma erlang è compilato da codice sorgente in un formato eseguibile da una macchina virtuale che non dipende dal sistema operativo e perciò, un programma erlang gira, senza nessuna modifica, su qualsiasi computer sul quale è stato installato Erlang/OTP. Da questo punto di vista, Erlang/OTP è molto simile Java Development Kit (JDK).

Una volta abbiamo l'interprete erlang installato, per lanciarlo basta il comando "erl" oppure "werl" se vogliamo la modalità grafica su Windows ed ecco che siamo pronti per iniziare. Vedi l'immagine (interprete.png)

Hello World

Il noto programma "Hello World" non ha bisogno di presentazione, ma ecco il codice:

-module(hello).
-export([hello_world/0]).

hello_world()->
    io:format("Hello World ~n").

Ora basta salvarlo nel file di testo "hello.erl" ed eseguire la seguente sequenza di comandi:
1) c(hello). %per la compilazione del codice
2) hello:hello_world(). %per la sua esecuzione

Notate quanto sia piccolo il file compilato "hello.beam" (circa 500 byte). L'estensione ".beam" è quella di un programma compilato erlang.

La prima riga del programma "-module(nome_programma)." è comune a tutti i programmi erlang, mentre "-export([lista_funzioni_da_esportare])" indica l'elenco delle funzioni da esportare. Nel nostro caso abbiamo una singola funzione "hello_world", il simbolo "/0" indica che la funzione non prende nessun parametro. Il risultato è visibile nell'immagine (hello.png)

Quick Sort

Quick Sort (Ordinamento Veloce) è il ben noto ed utilizzato algoritmo di ordinamento di strutture dati omogenei che è stato implementato praticamente in tutti i linguaggi di programmazione.

Quello scritto in C dovrebbe risultare il più veloce nell'esecuzione, almeno in linea teorica, ma senz'altro non è affatto più breve e più leggibile di quello scritto in Erlang.

/**** Compile QuickSort for strings ****/
#define QS_TYPE char*
#define QS_COMPARE(a,b) (strcmp((a),(b)))

/**** Compile QuickSort for integers ****/
//#define QS_TYPE int
//#define QS_COMPARE(a,b) ((a)-(b))

/**** Compile QuickSort for doubles, sort list in inverted order ****/
//#define QS_TYPE double
//#define QS_COMPARE(a,b) ((b)-(a))
void QuickSort(QS_TYPE list[], int beg, int end)
{
    QS_TYPE piv; QS_TYPE tmp;
    int  l,r,p;
    while (beg 0 ) ) r--;
            if (l>r) break;
            tmp=list[l]; list[l]=list[r]; list[r]=tmp;
            if (p==r) p=l;
            l++; r--;
        }
        list[p]=list[r]; list[r]=piv;
        r--;
        // Select the shorter side & call recursion. Modify input param. for loop
        if ((r-beg)<(end-l))
        {
            QuickSort(list, beg, r);
            beg=l;
        }
        else
        {
            QuickSort(list, l, end);
            end=r;
        }
    }
}
Ed ecco quello scritto in Erlang!
-module(quicksort).
-export([qsort/1]).

qsort([]) -> [];
qsort([Pivot|Rest]) ->
    qsort([ X || X <- Rest, X < Pivot]) ++ [Pivot] ++ qsort([ Y || Y <- Rest, Y >= Pivot]).

Ecco un breve commento. Ciascuno dei due programmi esprime nel proprio linguaggio l'algoritmo di ordinamento veloce applicato ad una lista di dati. Il codice scritto in Erlang è una mera traduzione letteraria dell'algoritmo che dice questo:
1. una lista vuota è già ordinata;
2. se la lista non è vuota, allora prendi il primo elemento (pivot) e metti tutti gli elementi della lista più piccoli del pivot a sinistra e quelli più grande a destra da creare due nuove liste. Rifare la medesima operazione per le sottoliste. Alla fine la lista sarà ordinata.

Per il programma scritto in C bisogna stare attendi ai tipi di dati perché debbono essere omogenei e perciò per ogni tipo di dato bisogna aggiustare le macro.

Con il codice scritto in Erlang, il programma, una volta compilato, funzionerà per tutti i tipi di dati senza nessuna ulteriore modifica anche quando si tratta di una lista di liste.

Ora compiliamo il codice
1> c(quicksort).
{ok,quicksort}

crea la prima lista:
2> A=[z,x,c,d,a].
[z,x,c,d,a]

crea la seconda lista:
3> B=[3,4,5,6,7,12,3.14159,2.71828].
[3,4,5,6,7,12,3.14159,2.71828]

crea la terza lista:
4> C=["Kira", "Patrizia", "Nawa"].
["Kira","Patrizia","Nawa"]

crea una lista come somma di liste:
5> D=[A,B,C].
[[z,x,c,d,a],
[3,4,5,6,7,12,3.14159,2.71828],
["Kira","Patrizia","Nawa"]]

ordina D:
6> quicksort:qsort(D).
[[3,4,5,6,7,12,3.14159,2.71828],
[z,x,c,d,a],
["Kira","Patrizia","Nawa"]]

ordina A:
7> quicksort:qsort(A).
[a,c,d,x,z]

ordina B:
8> quicksort:qsort(B).
[2.71828,3,3.14159,4,5,6,7,12]

ordina C:
9> quicksort:qsort(C).
["Kira","Nawa","Patrizia"]

ordina la lista di liste preordinate:
10> E=quicksort:qsort([quicksort:qsort(A), quicksort:qsort(B), quicksort:qsort(C)]).
[[2.71828,3,3.14159,4,5,6,7,12],
[a,c,d,x,z],
["Kira","Nawa","Patrizia"]]

A questo punto è doveroso citare due fatti basati su studi statistici e quindi obiettivamente validi. Il primo consisteva nel creare due gruppi di programmatori con background totalmente differenti ed assegnare a loro lo stesso compito; cioè scrivere una nuova applicazione utilizzando un linguaggio funzionale che nessuno dei due gruppi conosceva prima. I due gruppi hanno poi avuto la stessa formazione nell'apprendere il nuovo linguaggio. Il primo gruppo è composto da persone provenienti da discipline scientifiche e tecniche con precedenti esperienze nel campo della programmazione. Il secondo gruppo proviene da discipline umanistiche senza nessuna precedente esperienza nel campo della programmazione. A sorpresa di tutti, il secondo gruppo ha riportato migliori risultati.

Il fatto non dovrebbe, in realtà, destare stupore visto che tutti da sempre sanno che un approccio funzionale alla programmazione è molto più naturale di quello procedurale o ad oggetti. Il secondo fatto è che il numero di errori contenuti in un programma è proporzionale alla lunghezza del codice. Se, mediamente, un programma scritto in un linguaggio funzionale è di una lunghezza pari circa ad un quinto di quello scritto in un linguaggio procedurale o ad oggetti, allora ecco perché il software scritto in un linguaggio funzionale è meno bacato.

Liste

Il capostipite dei linguaggi funzionali è senza dubbio il LISP. Il nome è un acronimo derivato dal mix di due parole "List" e "Processing", ossia "processore di liste". In altri termini, alla base del LISP ci sono le liste ed un'ampia serie di procedure per manipolarle. Questa particolarità è comune a tutti i linguaggi funzionali ed Erlang non fa eccezione. Una lista è una sequenza di elementi numerici, caratteri, stringhe o miste. Una lista è composta da una testa che è il suo primo elemento e da una coda che è tutto il resto.

Ecco alcuni esempi di utilizzo di liste in Erlang:
A=[1, 2, 3, 4, 5, 314].
A è una lista di numeri naturali. La testa di A è l'elemento "1", mentre la sua coda è la lista [2, 3, 4, 5, 314].
B=[a1, a2, x1, "x2", "Ciao!", 3.14159, 2.71828].
B è una lista ibrida. La testa di B è l'elemento "a1", mentre la sua coda è la lista [a2, x1, "x2", "Ciao!", 3.14159, 2.71828].
C=[[1, 2], [a1, x1], "x2", ["Ciao!", [3.14159, 2.71828]]].
C è una lista ancora più ibrida visto che tra i suoi elementi troviamo altre liste. La testa di C è la lista [1, 2], mentre la sua coda è [[a1, x1], "x2", ["Ciao!", [3.14159, 2.71828]]].

In Erlang, le liste sono una struttura fondamentale per la gestione e la manipolazione di dati complessi in modo espressivo, semplice ed efficiente. Il linguaggio offre delle istruzioni molto sofisticate gestire le liste.

Tuple

Insieme alle liste, Erlang dispone di un'altra struttura per la gestione e la manipolazione dei dati chiamata tuple.

Una tuple è una sequenza di elementi separati da virgola e racchiusi tra parentesi graffa. Una tuple potrebbe essere vuota oppure contenere uno o più termini.

Ecco alcuni esempi di tuple:
A={1,2,3,4,5}.

B={1, a1, x1, "x2", "Ciao!", 3.14159, 2.71828}.

C={1, {a1, x1}, "x2", {"Ciao!", {3.14159, 2.71828}}}.

È possibile definire anche strutture di dati come combinazioni di liste e tuple.
D=[1, {a1, x1}, "x2", {"Ciao!", [3.14159, 2.71828]}].

La combinazione di liste e tuple con pattern matching sono alla base della programmazione in Erlang e rendono il codice estremamente breve, leggibile ed elegante. Da qui deriva, infatti, la brevità dei programmi scritti in Erlang.

Pattern matching

Erlang usa intensamente il pattern matching per confrontare il flusso delle variabili, delle funzioni e dei loro parametri. Questo meccanismo è molto potente e permette di scrivere codice molto breve ed espressivo. Ecco alcuni esempi.

{A,B} = {"lettera A", "lettera B"}.
Equivale ad attribuire alla variabile A il valore "Lettera A", mentre a B il valore "lettera B".

{C,D} = {"lettera C", [ciao, mondo, 3.14, "Ciao Mondo!"]}.

In questo caso la variabile C vale "lettera C", mentre la variabile D vale l'intera lista [ciao, mondo, 3.14, "Ciao Mondo!"].

Grazie al pattern matching, è possibile fare delle combinazioni molto complesse di espressioni e variabili mantenendo un codice estremamente elegante e leggibile. Per chi si avvicina per la prima volta ad Erlang, è vivamente consigliabile impossessarsi quanto prima di questo meccanismo grazie al quale sarà possibile scrivere applicazioni brevi e molto interessanti in poco tempo.

Programmazione sequenziale

Erlang, per concezione, è un linguaggio parallelo, ma è anche adatto a scrivere applicazioni sequenziali. Ecco alcuni esempi, giusto per vedere l'aspetto di un programma Erlang.

-module(math1).
-export([fact/1]).
% questa è una linea di commento
% fact/1 significa che la funzione fact prende un singolo argomento
fact(0) -> 1;
fact(N) -> N * fact(N-1).

Per la compilazione del modulo "math1" dalla shell di Erlang basta digitare: c(math1). seguito da invio (non dimenticare il punto dopo la parentesi perché è necessario)
{ok,math1}
Poi per la prova inserire:
math1:fact(5).
120
che è il risultato finale.

Ecco un altro esempio leggermente più complicato che risolve un'equazione di secondo grado. Notare che le variabili iniziano con lettere maiuscole, mentre gli atomi con lettere minuscole.

-module(math1).
-export([fact/1, quadratic/3]).
% la funzione quadratic prende 3 argomenti
quadratic(A,B,C) -> D = B*B-4*A*C,
if D < 0 -> no_real_solution;
true ->
  X1 = 0.5*(-B-math:sqrt(D))/A,
  X2 = C/(A*X1), 
{X1,X2}
end.

c(math1).
{ok,math1}
math1:quadratic(2,-3,1).
{0.500000,1.00000}
math1:quadratic(1,2,3).
no_real_solution

Ora dando questo input:
A1=2, A2=-3, A3=1.
math1:quadratic(A1,A2,A3).
{0.500000,1.00000}

Se invece riscriviamo:
A1=5, A2=-3, A3=1.

Otteniamo un messaggio di errore perché non si può assegnare ad A1 un valore diverso da 2. Una variabile in Erlang può essere assegnata una sola volta. Questo meccanismo potrebbe sembrare una lacuna del linguaggio, ma una volta presa la confidenza con questa apparente limitazione si vedranno poi solo i vantaggi. Il più grande vantaggio della singola assegnazione è quello di rendere facile l'esecuzione parallela di alcune operazioni. In più, una variabile non sovrascritta mantiene sempre un unico valore e così è molto facile controllare il suo contenuto. Le operazioni di debug diventano in questo caso estremamente semplici.

Programmazione parallela

Erlang si distingue dagli altri linguaggi di programmazione per il suo spiccato orientamento allo sviluppo di applicazioni distribuite e parallele. Questo parallelismo viene fornito nativamente dal linguaggio tramite processi e la comunicazione tra gli stessi. La reazione di un processo e la comunicazione con un altro processo è sempre esplicita. I processi di Erlang fanno parte del linguaggio e sono ben diversi dai thread forniti dal sistema operativo.

Un processo è una singola entità autonoma che non appartiene a nessun gruppo o gerarchia, non condivide nessun dato e può solo comunicare con altri processi tramite messaggi.

Per creare ed iniziare l'esecuzione di un processo, oppure controllare un insieme di attività parallele, Erlang usa 3 parole chiavi che sono: spawn, send e receive insieme al punto esclamativo (!). La funzione spawn prende 3 parametri in input ed inizia l'esecuzione di una computazione parallela chiamata processo che viene identificato tramite il valore unico di ritorno della funzione. È possibile a questo punto spedire/ricevere un messaggio al/dal processo in esecuzione.

L'istruzione Pid ! Msg permette di spedire il messaggio Msg al processo identificato da Pid. Per esempio:

La funzione receive, invece, permette di ricevere uno o più messaggi da processi in esecuzione. Ecco come viene utilizzata:

receive
  Messaggio1 ->(al ricevimento del massaggio esegue...
    ...
  Messaggio2 ->
    ...
  Messaggio3 ->
    ...
end.

Ecco un piccolo esempio che illustra la semplicità di utilizzo di questo linguaggio per lo sviluppo di applicazioni parallele e distribuite. Si tratta di un semplice esempio di "Ping Pong" dove la funzione "ping" invia un messaggio a "pong" che, una volta ricevuto, invia una risposta a "ping". L'operazione dura per tre volte.

-module(pingpong).
-export([start/0, ping/2, pong/0]).

1. ping(0, Pong_PID) ->
    Pong_PID ! finished,
    io:format("ping finished~n", []);

2. ping(N, Pong_PID) ->
    Pong_PID ! {ping, self()},
    receive
        pong ->
            io:format("Ping received pong~n", [])
    end,
    ping(N - 1, Pong_PID).

3. pong() ->
    receive
        finished ->
            io:format("Pong finished~n", []);
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.

4. start() ->
    Pong_PID = spawn(pingpong, pong, []),
    spawn(pingpong, ping, [3, Pong_PID]).
      

1. quando il numero del messaggio diventa zero, fermati;
2. se il numero del messaggio ricevuto è maggiore di zero segnala l'arrivo del messaggio ed avvisa "pong".
3. se il messaggio ricevuto da "ping" è "finished" allora fermati, altrimenti segnala l'arrivo del messaggio a "ping" che rimane in attesa;
4. crea un processo con identificatore "Pong_PID" e nome "pong" ed esegue la funzione "spawn" che crea il processo "ping" con 3 messaggi da inviare a "pong".

Eseguendo questo programma su un singolo computer (singolo nodo), otteniamo una sequenza di output che corrisponde ai messaggi scambiati tra il processo "ping" e "pong" (vedi immagine "pingpong.bmp").

Ora questo programma verrà leggermente modificato per girare su due nodi:

-module(ping_pong).
-export([start_ping/1, start_pong/0,  ping/2, pong/0]).

ping(0, Pong_Node) ->
    {pong, Pong_Node} ! finished,
    io:format("ping finished~n", []);

ping(N, Pong_Node) ->
    {pong, Pong_Node} ! {ping, self()},
    receive
        pong ->
            io:format("Ping received pong~n", [])
    end,
    ping(N - 1, Pong_Node).

pong() ->
    receive
        finished ->
            io:format("Pong finished~n", []);
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.

start_pong() ->
    register(pong, spawn(ping_pong, pong, [])).

start_ping(Pong_Node) ->
    spawn(ping_pong, ping, [3, Pong_Node]).

Compiliamo il codice con il comando "c(ping_pong)." ed ora creiamo due nodi erlang chiamati "ping" e "pong" su un unico computer. Da una finestra DOS oppure da un terminale linux eseguiamo il comando:

erl -sname pong

e vediamo apparire:

Erlang (BEAM) emulator version 5.5.5 [async-threads:0]

Eshell V5.5.5  (abort with ^G)
(pong@tanit)1>           

ripetiamo lo stesso procedimento di prima per creare il nodo "ping".

erl -sname ping

e vediamo apparire:

Erlang (BEAM) emulator version 5.5.5 [async-threads:0]

Eshell V5.5.5  (abort with ^G)
(ping@tanit)2>           

ora iniziamo il processo "pong":

(pong@tanit)1>ping_pong:start_pong().
true

ed ora tocca a "ping":

(ping@tanit)1> ping_pong:start_ping(pong@tanit).
<0.38.0>
Ping received pong
Ping received pong
Ping received pong
ping finished  

Il procedimento ed il codice rimane praticamente lo stesso anche quando si tratta di computer diversi, basta che siano collegati in rete. Questo risultato è del tutto naturale se consideriamo che la comunicazione avviene tra i "un nodo". Cioè, una volta che è stato lanciato l'interprete erlang si crea automaticamente il corrispondente "nodo". Grazie a questo meccanismo, un'applicazione distribuita si traduce in una serie di comunicazioni tra "nodi" erlang tramite messaggi, mentre la localizzazione geografica del computer perde significato. È possibile creare più nodi sullo stesso computer. Da qui appare evidente quanto sia Erlang adatto e naturale a scrivere applicazioni server.

L'unica cosa da tenere in considerazione è quella di creare dei cookie contenenti lo stesso valore (una parola segreta) su tutti computer comunicanti. In pratica questo si traduce nella creazione di un file di testo nascosto nella directory "home" con il nome ".erlang.cookie". Nel file bisogna inserire la parola segreta. Questo meccanismo è necessario per ragioni di sicurezza per impedire l'accesso non autorizzato ad un nodo.

Erlang e Applicazioni desktop

Erlang non è il linguaggio più indicato per sviluppare applicazioni desktop, soprattutto quando si tratta della piattaforma Windows dove ci sono ottimi strumenti maturi, avanzati e collaudati. Comunque, per chi lo desidera fare, ci sono tre diverse soluzioni. La prima consiste in un biding della nota libreria GTK (http://erlgtk.sourceforge.net/) che permette di accedere a tutte le risorse grafiche della libreria da codice erlang. La seconda invece è quasi nativa visto che è presente nella distribuzione ufficiale. Si tratta di Graphics System (GS), un semplice sistema grafico per scrivere applicazioni con un'interfaccia grafica sobria ed essenziale.

Ecco uno dei tanti piccoli esempi forniti dalla piattaforma Erlang/OTP. Vedi immagine (entry.png e entry_out.png).

-module(entry_demo).

-export([start/0,init/1]).

start() ->
    spawn(entry_demo,init,[self()]),
    receive
	{entry_reply,Reply} -> Reply
    end.

init(Pid) ->
    S=gs:start(),
    Win=gs:window(S,[{title,"Entry Demo"},{width,150},{height,100}]),
    gs:create(label,Win,[{width,150},{label,{text,"What's your name?"}}]),
    gs:create(entry,entry,Win,[{x,10},{y,30},{width,130},{keypress,true}]),
    gs:create(button,ok,Win,[{width,45},{y,60},{x,10},{label,{text,"Ok"}}]),
    gs:create(button,cancel,Win,[{width,60},{y,60},{x,80},{label,{text,"Cancel"}}]),
    gs:config(Win,{map,true}),
    loop(Pid).

loop(Pid) ->
    receive
	{gs,entry,keypress,_,['Return'|_]} ->
	    Text=gs:read(entry,text),
	    Pid ! {entry_reply,{name,Text}};
	{gs,entry,keypress,_,_} ->
	    loop(Pid);
	{gs,ok,click,_,_} ->
	    Text=gs:read(entry,text),
	    Pid ! {entry_reply,{name,Text}};
	{gs,cancel,click,_,_} ->
	    Pid ! {entry_reply,cancel};
	X ->
	    io:format("Got X=~w~n",[X]),
	    loop(Pid)
    end.

Da sottolineare come, nello stile erlang, la comunicazione tra le parti avviene tramite processi e messaggi anche quando si tratta di semplicissime applicazioni grafiche. Quando lanciate questo programmino noterete che l'applicazione vi chiede di accedere alla rete. Non si tratta di uno spyware ma il fatto è del tutto normale quando si tratta di Erlang dove quasi tutte le applicazioni sono già predisposte ad essere distribuite.

La terza soluzione di sviluppo di applicazioni desktop in Erlang è la più professionale, ma è anche la più impegnativa. Si tratta di usare la libreria ESDL, ossia Erlang insieme alle librerie SDL (http://www.libsdl.org/) e OpenGL (http://www.opengl.org). Qui, l'unico limite, ed è veramente il caso di dirlo, è la fantasia. In effetti, il modellatore solido Wings3D utilizza questa potentissima combinazione ed è tra i primi della sua categoria per qualità e performance.

Erlang e RDBMS

Un linguaggio di programmazione è quasi inutilizzabile se è privo della possibilità di accedere ad un database server relazionale. Erlang permette di accedere alla maggior parte dei database server relazionali più noti. L'accesso avviene tramite ODBC oppure tramite driver nativi come nel caso di MySQL e PostgreSQL.

Una ulteriore possibilità di accesso ad un RDBMS consiste nel comunicare direttamente con il server tramite la porta TCP di ascolto. In generale questa porta è nota (Firebird, per esempio, comunica tramite la porta TCP 3050). Quindi, grazie a questo meccanismo, è possibile, dopo essersi autenticati, eseguire delle query SQL su un database server da un programma scritto in Erlang. Comunque, la presenza del database server nativo Mnesia compreso nella OTP rende l'utilizzo di un RDBMS esterno quasi superfluo.

Le qualità e la sinergia offerte utilizzando Erlang e Mnesia sono difficilmente riscontrabili in altre soluzioni.

Solo nel caso in cui abbiamo già dei dati contenuti in un RDBMS da utilizzare da una nuova applicazione scritta in Erlang c'è l'esigenza di creare un canale di comunicazione tra l'applicazione ed il database.

Applicazioni scritte in Erlang

Erlang è attualmente utilizzato da diverse software house impegnate nello sviluppo di applicazioni strategiche di telecomunicazioni e non solo. Basta andare a curiosare nei messaggi della mailing-list dedicata al linguaggio per capire l'importanza e l'utilizzo di questa piattaforma.

Una delle applicazioni che ho avuto il modo di utilizzare ed apprezzare molto è il server di messaggeria real-time e fault-tolerant "ejabberd" (http://www.ejabberd.im/); funziona su un singolo server o su un cluster e ha delle caratteristiche veramente eccezionali. È, tra l'altro, freeware, open source e si installa in pochi minuti! Invito chiunque a provarlo per capire la potenza e l'eleganza di un'applicazione scritta in Erlang/OTP.

Un'altra applicazione che ho avuto modo di usare è Mnesia.
Mnesia è un DBMS (Database management System) distribuito e replicante dedicato a gestire dati in continue modifiche e real-time. Mnesia è interamente scritto in Erlang (circa 20.000 linee di codice) e fa parte della piattaforma OTP. Non è un database relazionale dove i dati sono rigidamente strutturati in tabelle, ma è un contenitore dove i dati possono essere espressi in diversi formati non strutturati. Le query sul database sono eseguite nello stesso linguaggio Erlang. Questo meccanismo offre un'estrema flessibilità e performance nella gestione e la manipolazione dei dati. Possiamo dire che Erlang sta a Mnesia quello che SQL sta ad un database relazionale.

Il web server YAWS (Yet An other Web Server) non fa parte della distribuzione ufficiale della OTP, perciò bisogna installarlo come un programma esterno. YAWS è scritto interamente in Erlang ed è molto veloce ed elegante. Si integra stupendamente con Mnesia ed utilizza Erlang come linguaggio di estensione e per scrivere plug-in. YAWS offre le stesse funzionalità di Apache ma quando si tratta di contenuti dinamici o di elevate connessioni simultanee allora YAWS mette in evidenza tutta la sua potenza (vedi immagine di confronto "apachevsyaws.jpg"). C'è una pagina web (http://www.sics.se/~joe/apachevsyaws.html) dedicata ad un confronto tra Apache e YAWS.

Erlang eccelle nello sviluppo di applicazioni server e non è affatto indicato per scrivere applicazioni di grafica ed in generale dove il calcolo numerico è una parte importante dell'applicazione. Stranamente, Wings3D (http://www.wings3d.com/) è un'applicazione multipiattaforma di modellazione solida completa, stabile e facilissima da usare, scritta in Erlang. Wings3D è usato dagli esperti di grafica che lo giudicano pari e a volte anche superiore a molte applicazioni commerciali del settore scritte in C/C++. Le sue performance sono ottime. Ecco un modello 3D realizzato con Wings3D (vedi le immagini "wings3d.jpg", "soldier.jpg" e "modello.bmp"). Essendo Wings3D un'applicazione Erlang, è possibile eseguire qualsiasi codice Erlang dall'interprete sottostante ed estendere Wings3D con plugin con molta facilità e senza limiti. Wings3D è una dimostrazione di forza di come è possibile scrivere una validissima applicazione in campo quasi proibito ad Erlang.

Conclusione

Se vi trovate bene con il PHP, PERL, Ruby, Python, Java o qualsiasi altro linguaggio a scrivere le vostre applicazioni web o altro, allora potete completamente ignorare Erlang. A squadra vincente si cambia.

Erlang insieme alla piattaforma OTP diventa invece lo strumento ideale e quasi senza rivali quando si tratta di scrivere complesse applicazioni server che debbono funzionare correttamente per lunghi periodi e senza interruzioni anche in presenza di errori; che siano poi di tipo telecom, client/server, web o altro ha veramente poca importanza.

Diventare produttivi con Erlang/OTP richiede un impegno non indifferente vista la complessità, la ricchezza della piattaforma e l'apparente "stranezza" del linguaggio. Perciò, Erlang/OTP è consigliato per gli audaci, per chi accetta la sfida e per chi vuole investire nella conoscenza a lungo termine. In queste condizioni, programmare in Erlang è sinonimo di programmare il futuro.

Sito ufficiale di Erlang