Parlare alle macchine

PHP?

Stando al piano editoriale del nostro blog avrei dovuto scrivere un articolo che presentasse il linguaggio di programmazione PHP.

Però l’altra sera, a cena con amici in un ristorante indiano qui a Madrid, mi sono reso conto che il concetto stesso di linguaggio di programmazione merita un articolo a sé, evidentemente previo a quello programmato sul PHP.

A tavola non abbiamo parlato direttamente di linguaggi di programmazione, ma del concetto di algoritmo, e lì ho capito quale slittamento semantico abbia subito questo vocabolo.

Alla domanda: “Ma cos’è un algoritmo?”, io stavo timidamente rispondendo che un algoritmo è qualcosa che possiamo paragonare a una ricetta (in fondo eravamo in un ristorante), ma altri commensali intervennero con serietà paventando le mille insidie della tecnologia digitale, animata da mostruosi fantasmi chiamati, appunto, Algoritmi che stanno attentando alla nostra libertà e autodeterminazione.

Algoritmi e linguaggi di programmazione

Non voglio minimizzare i pericoli della tecnologia digitale globale e le minacce dello Stato Imperialista delle Multinazionali (da cui l’acronimo SIM, il chip tramite il quale i nostri telefoni ci controllano… oh, sto scherzando!). Però vorrei dedicare un migliaio di caratteri per parlarvi dei linguaggi di programmazione con lo stesso pacato trasporto con il quale un meccanico vi parlerebbe delle meraviglie di una Ducati Supersport.

Poiché se l’algoritmo è la ricetta e il computer è il cuoco con la sua cucina, il linguaggio di programmazione è la particolare espressione linguistica della ricetta/algoritmo. Infatti, come una ricetta può essere espressa in arabo, in italiano, in cirillico, con ideogrammi cinesi, insomma, con simboli, grammatiche e sintassi differenti, così un algoritmo può essere espresso in Prolog, PHP, Smalltalk, Ada, Python o Go, tanto per fare un elenco naif di linguaggi di programmazione.

PHP

L’importante è capirsi

Possiamo parlare con le macchine? Ma, penso che dobbiamo chiederci cosa significa parlare. Perché mi chiedo: è possibile parlare con il proprio cane, è possibile parlare con il proprio gatto? Che significa parlare con il cane, con il gatto? In genere mi pare che significhi ottenere che il cane ci dia l’impressione di eseguire un nostro ordine o che il gatto ci dia l’illusione che ci stia prestando una qualche minima attenzione… Si tratta di passare dal linguaggio della nostra logica ai meccanismi istintuali del nostro amico a quattro zampe affinché da lui ci ritorni qualcosa che stia nel campo della nostra logica.

Con le macchine le cose vanno un po’ allo stesso modo. Non posseggono meccanismi istintuali, ma posseggono di base meccanismi binari (a meno che non si tratti di macchine quantistiche), che possono essere combinati a grande velocità per ottenere operazioni di spostamento di porzioni di dati da una memoria a un registro, e quindi operazioni aritmetiche o logiche sui valori contenuti in tali registri, e poco più. Le macchine possono controllare dispositivi di uscita, come ad esempio uno schermo, sui quali ci ritornano cose che stanno nel campo della nostra comprensione.

Parlare alle macchine, cioè cercare di ottenere da loro lo svolgimento di compiti secondo le nostre necessità/desideri, significa tradurre la nostra logica nelle operazioni elementari che i circuiti elettronici della macchina possono eseguire.

“C’era tra loro l’interprete”

I suoi fratelli non sapevano che Giuseppe li capiva, perché tra lui e loro vi era l’interprete (Gen 42,23)

Raramente (forse mai) un tecnico informatico “parla” direttamente alle macchine computazionali, traducendo il nostro linguaggio umano nei meccanismi elettronici (e meccanici) della macchina. E oggi molto meno che nel passato, quando magari si perforavano a mano le schede che venivano inserite nel lettore meccanico della macchina per alimentare i processi computazionali.

Un tecnico informatico in genere parla (e lo fa scrivendo un testo) a un “interprete” che a sua volta produce un testo che più o meno direttamente viene “compreso” dalla macchina. Ma in genere lo stesso interprete “parla” con altri interpreti che via via producono testi sempre più prossimi al modo di “intendere” della macchina.

Questi interpreti possono essere simultanei (e allora li chiamiamo propriamente interpreti), cioè interpreti che leggono il testo del programmatore e lo passano alla catena di interpreti che fanno eseguire alla macchia le istruzioni, oppure possono essere interpreti consecutivi (e allora li chiamiamo compilatori), cioè leggono il testo del programmatore e creano un testo che successivamente sarà usato per alimentare la computazione della macchina, passando eventualmente attraverso altri interpreti/compilatori.

Che ora è?

Facciamo un esempio. Possiamo chiedere alla macchina di “scrivere” sullo schermo l’ora corrente.
L’algoritmo è molto semplice:

  1. Chiedo alla macchina, tramite un funzione, l’ora corrente e memorizzo il risultato in una variabile. Il valore sarà un numero intero;
  2. Ottengo da questo valore i valori della data e dell’orario in un formato più usuale per noi umani, e metto questi valori in due variabili;
  3. Utilizzo una funzione di stampa a terminale per visualizzare i valori ottenuti e una frase di descrizione.

L’interprete PHP

Usando il linguaggio PHP il programmatore scriverà un testo con queste proposizioni:

<?php

// 1.

$tempoCorrente = new DateTime(“Europe/Rome”);

// 2.

$oraCorrente = IntlDateFormatter::formatObject($tempoCorrente,’H:mm:ss’);

$dataCorrente = IntlDateFormatter::formatObject($tempoCorrente,’d/L/Y’,’it_IT.utf8′);

// 3.

echo “La data e l’ora corrente sono: “.$dataCorrente.” “.$oraCorrente.PHP_EOL;

?>

Quindi utilizzerà un interprete PHP per fare intendere alla macchine le istruzioni, e ricevere una risposte:

$ php -f ora.php

La data e l’ora corrente sono: 16/7/2023 18:47:28

Semplice, no?

PHP

Una linea di comando

In realtà la cosa è più complessa. Occorre infatti che qualcuno o qualcosa dica alla macchina di usare l’interprete del linguaggio PHP per tradurre delle istruzioni PHP in istruzioni per la macchina.
Questo “qualcuno/qualcosa” è a sua volta un “interprete”.

Ci si riferisce a questo interprete come a una interfaccia a riga di comando (CLI, command-line interface). Nei sistemi Unix, e ormai non solo in quelli, ci si riferisce a questi interpreti con il nome di shell.

La shell resta in attesa delle nostre istruzioni, mostrandoci la sua disponibilità attraverso un simbolo, ad esempio:

$

Le nostre istruzioni sono espresse come una serie di parole. Nel nostro caso la sequenza di parole è:

php -f ora.php

La prima parola è il nome del comando da eseguire, le parole successive sono i parametri usati nell’esecuzione del comando. In questo modo è come se dicessimo all’interprete: fai eseguire il comando php con i parametri -f e ora.php. Per come funziona il comando php la combinazione dei parametri -f ora.php sta a significare che l’interprete PHP traduca le istruzioni contenute nel testo di nome ora.php.

Fare altrimenti

Ohibò, visto che la shell è a sua volta un interprete, basterà che nell’armamentario del sistema operativo ci sia un comando per stampare le date affinché si possa fare in altro modo quello che abbiamo fatto tramite PHP.

E infatti possiamo inserire nella riga di comando questo testo:

echo “La data e l’ora corrente sono:” `date +”%d/%m/%Y %H:%M:%S”`

e otterremo lo stesso risultato:

La data e l’ora corrente sono: 16/07/2023 19:37:21

Beh, non è proprio lo stesso risultato perché è passato un po’ di tempo…
Come si vede l’algoritmo è stato espresso con un linguaggio differente, più conciso e un po’ meno comprensibile, nel quale a mala pena riusciamo a distinguere i tre punti che abbiamo elencato sopra.

Usiamo un compilatore

Abbiamo visto due esempi di linguaggi tradotti con il sistema della “simultanea”. Il testo con le istruzioni viene tradotto in qualcosa di comprensibile per la macchina nel momento in cui si fa richiesta dell’esecuzione di quel determinato programma. Questa traduzione avviene ogni volta che si richiede l’esecuzione.

I linguaggi che prevedono l’uso di un compilatore seguono un altro approccio, quello della traduzione consecutiva memorizzata una volta per tutte.

Il programmatore scriverà un testo (chiamiamo questo testo ora.c) inserendo queste istruzioni:

#include

#include

int main() {

time_t tempo;

struct tm *data_ora;

/* 1. */

time(&tempo);

data_ora = localtime(&tempo);

/* 2. */

char buffer[80];

strftime(buffer, 80, “La data e l’ora corrente sono: %d/%m/%Y %H:%M:%S\n”, data_ora);

/* 3. */

printf(“%s”, buffer);

return 0;

}

Possiamo notare che nei linguaggi compilati le variabili devono essere preannunciate prima di essere usate, e se ne deve dichiarare il “tipo”. Anche per questo il testo di un linguaggio compilato è in genere meno conciso del testo corrispondente di un linguaggio interpretato.

Scritto che abbia il testo, il programmatore avvierà dalla shell il programma di compilazione (il “traduttore consecutivo”) per tradurre il testo in linguaggio C:

cc -o ora ora.c

Il programma cc non eseguirà le istruzioni contenute nel testo ora.c, creerà invece un testo (ora) contenente le stesse istruzioni però in un linguaggio che con una certa approssimazione possiamo dire vicino a quello compreso dalla macchina.

Così facendo avremo aggiunto al sistema operativo un nuovo comando che possiamo eseguire tramite la shell:

ora

La data e l’ora corrente sono: 16/07/2023 20:08:46

Beh, non si tratta di una grande aggiunta… Però con questo meccanismo e con algoritmi più complicati potremmo realizzare nuovi comandi più complessi e funzionali.

Vantaggi e svantaggi

Ciascuno dei due approcci presenta dei punti di forza e delle debolezze. Per farla breve, un linguaggio interpretato permette maggiore agilità, infatti il codice scritto è immediatamente eseguibile tramite l’interprete, ed è eseguibile su tipologie di macchine differenti, purché ci sia un interprete per quella macchina particolare.

Al contrario un linguaggio compilato tendenzialmente offre migliori prestazioni, a fronte di una fase di sviluppo più complessa. Le prestazioni migliori sono dovute al fatto che in esecuzione si mandano direttamente istruzioni “comprese” dalla specifica macchina per la quale si è compilato il programma. Questo implica che lo stesso programma va compilato nuovamente nel caso volessimo utilizzare un altro tipo di macchina che si va ad utilizzare, e questo implica minore immediatezza.

Mi pare di poter dire che oggi si prediligono i linguaggi interpretati, proprio a motivo della loro agilità, anche considerando la grande capacità di calcolo che hanno le macchine attuali . Senza contare che lo strumento che probabilmente usiamo di più nell’interazioni con le macchine, intendo dire il browser, è praticamente un interprete fornito di capacità grafiche e sonore. I browser, infatti, sono essenzialmente degli interpreti dei linguaggi HTML, CSS e Javascript.

Evidentemente, quando le prestazioni in termini di velocità sono imprescindibili, i linguaggi compilati sono tuttavia ancora preferibili: il compilatore produce un “testo di istruzioni” ritagliato su misura per quella specifica macchina che eseguirà quelle istruzioni.

Posso infine aggiungere che i due approcci non sono due mondi separati, e non da oggi. Il linguaggio Pascal, ad esempio, fu progettato per essere un linguaggio compilato, ma nelle sue prime implementazioni il risultato della compilazione non era codice direttamente compreso dalla macchina, ma un codice intermedio (P-code) eseguibile da un interprete. In questo modo il testo in P-code, efficiente in fase di esecuzione poiché ottimizzato nella fase di compilazione, è utilizzabile su macchine differenti, purché fornite dell’opportuno interprete.

È quello che accade con il linguaggio Java, che necessita una fase di compilazione e di una java virtual machine, un interprete che fa da intermediario con il linguaggio delle macchine.

PHP!

Mi accorgo di aver superato il migliaio di caratteri che mi ero proposto di spendere: è il momento di chiudere.

Chiudiamo allora con il linguaggio PHP, a cui questo articolo doveva essere dedicato…
Illustrando l’esempio precedente ho un pochino semplificato le cose. PHP è un linguaggio interpretato, lo abbiamo visto, ma l’esecuzione del codice PHP passa attraverso una fase di compilazione: l’interprete PHP genera un codice intermedio più efficiente in esecuzione (qualcosa di simile al P-code a cui prima abbiamo accennato) che viene interpretato/eseguito da un ulteriore strato software (il motore Zend ) che fa da intermediario con la macchina.

Questo modello di implementazione fa di PHP un linguaggio allo stesso tempo agile ed efficiente. PHP nacque per uno scopo preciso, un’applicazione tutto sommato non molto sofisticata, ma quasi subito è divenuto uno strumento eccellente per la programmazione di applicativi complessi.
Però tutto questo lo vedremo in un prossimo articolo.

A presto!

Stefano Deponti