venerdì 26 dicembre 2008

Per andare dove devo andare, dove devo andare?

E sì, Totò avrebbe avuto bisogno di una bella mappa dettagliata delle strade di Milano per “andare dove doveva andare”. Avere una mappatura dettagliata del “territorio” è essenziale per avere la conoscenza completa di un sistema. Sia questo formato da strade o da file. Un compilatore deve avere questa conoscenza se vuole compilare e linkare un certo numero di file in un unico eseguibile.
Anche lui ha bisogno di una mappa per muoversi all'interno dei file che formano un progetto, soprattutto quando questo è formato da un considerevole numero di “translation unit”. Vediamo cosa lo standard dice a proposito delle translation unit. All'inizio del capitolo 2 del documento di standardizzazione del C++ ISO/IEC 14882:2003 possiamo leggere:

"A source file together with all the headers and source files included via the preprocessing directive #include, less any source lines skipped by any of the conditional inclusion preprocessing directives, is called a translation unit".

È intuitivo, a questo punto, intravvedere la complessità del lavoro che deve svolgere il compilatore ed il linker per redarre la mappa della translation unit, soprattutto per progetti che comprendono un numero elevato di file.
È altrettanto intuitivo che una translation unit è una mappatura logica che non rispecchia la divisione fisica dei sorgenti nel file system. Abbiamo quindi un salto di livello logico da “territorio” a “mappa”.
In più, il linker ha un altro compito. Come si legge al paragrafo 2.1.9 del documento di standardizzazione sopra citato. Durante la fase di linking:

"All external object and function references are resolved. Library components are linked to satisfy external references to functions and objects not defined in the current translation. All such translator output is collected into a program image which contains information needed for execution in its execution environment".

Per continuare il nostro paragone, diciamo che in più il linker aggiunge al nostro stradario cittadino anche tutte quelle strade di periferia che si collegano con le strade del centro e che non farebbero parte del nostro “tuttocittà”.

Il sistema compilatore + linker, in un progetto ben formato, sa sempre in ogni istante cosa è situato dove e soprattutto “dove deve andare per dove deve andare”.

Luca Ciciriello

lunedì 24 novembre 2008

Programmation MultiParadigme...

...o PMP è il nome di un progetto di studio che ha origine ai “Leibnitz Laboratory” a Grenoble in Francia e che ha come obiettivo lo sviluppo di una piattaforma unica per lo studio dei fondamenti della computer science. Infatti, come si può leggere nel sito ufficiale del progetto (http://www-leibniz.imag.fr/PMP/ che consiglio vivamente di guardare):

L'objectif de l'équipe Programmation Multiparadigme (PMP) est l'étude des fondements théoriques et la mise en oeuvre pratique d'une plate-forme expérimentale dédiée à la programmation multiparadigme. Nous considérons particulièrement l'intégration des paradigmes de programmation logique, fonctionnelle, impérative et concurrente.

Come più volte ho ribadito in questo blog, nessun paradigma da solo dà la soluzione a quella che ormai è stata definita (fino alla nausea) la crisi del software. L'OOP risolve molti problemi ma non tutti. D'altro canto se si vogliono ottenere altissime prestazioni, difficilmente si può rinunciare alla programmazione procedurale. Linguaggi “evoluti” come Java o C# sono definiti puri, nel senso che supportano esclusivamente l'OOP (sono detti puri anche se poi effettivamente danno il supporto anche per la programmazione concorrenziale).
Contrariamente ai linguaggi puri, esistono i linguaggi “ibridi”, che supportano più paradigmi di programmazione. Esempi di questi linguaggi sono il C++ ed il Python. Il C++ supporta l'OOP, la programmazione procedurale, la programmazione template e la programmazione concorrenziale. I primi tre paradigmi sono nativi del linguaggio, mentre la programmazione concorrenziale si appoggia a librerie esterne (POSIX per sistemi UNIX-like o Win32 e .NET per sistemi Windows). Nella prossima versione della standardizzazione del C++ il cui nome in codice è C++0x (che probabilmente prenderà il nome definitivo di C++09 visto che verrà rilasciata nel 2009), si pensa che anche la programmazione concorrenziale diventerà nativa nel linguaggio. La computer science è una scienza ancora relativamente giovane ed i campi di studio aperti sono ancora molti. Una delle frontiere è rappresentata proprio dallo studio della programmazione concorrenziale (concurrency programming). In questo blog ritornerò spesso sulla concurrency programming e sulla sua modellizzazione (Petri net e net theory in generale). Lo studio della programmazione concorrenziale è un campo ancora del tutto aperto e multi-disciplinare (fisica, matematica, logica, teoria dell'informazione, scienze cognitive, ecc) ed è affascinante come lo è ogni studio di frontiera. Dalla fisica, la programmazione concorrenziale eredita l'impredicibilità dei sistemi dinamici e della teoria del caos, spingendoci ad abbandonare l'idea che in ogni istante si possa sempre conoscere lo stato di un processo di elaborazione. Anche i programmatori più esperti e con più esperienza temono la programmazione concorrenziale. A volte ci vogliono letteralmente dei mesi per capire alcuni comportamenti dei propri programmi multithreding, ed a volte, non li si capirà mai. Un programma, se non attentamente studiato e programmato, può andare in errore in maniera imprevedibile anche dopo diversi anni dal suo rilascio. Nessun tipo di test può evidenziare tutte le insidie nascoste in un programma che fa uso di tecniche concorrenziali.

Bene. Per ora mi fermo qui ripromettendomi (se troverò il coraggio) di analizzare in qualche prossimo post alcune delle basi teoriche della programmazione concorrenziale.

Luca Ciciriello

lunedì 3 novembre 2008

Transizioni

Ultimamente mi è successa una cosa che mi ha fatto riflettere. Come mi pare di aver già accennato in un post precedente, mia moglie ed io, a casa, utilizziamo solo sistemi Macintosh. Io come piattaforma di programmazione, e lei come la maggior parte di qualsiasi utente, quindi per la navigazione in internet, per l’home-banking, la mail, le chat, fotoritocco, attività ludiche in generale, ecc. Per la mia attività di programmazione io uso un portatile Apple, mentre mia moglie utilizza una postazione Apple fissa. Più o meno recentemente la Apple ha aggiornato il suo sistema operativo MacOS X dalla versione 10.4 (Tiger) alla versione 10.5 (Leopard). Apple è stata da sempre una ditta che ha fatto dell’innovazione la sua vision aziendale. Molte volte, questa innovazione è andata a discapito della retro compatibilità fra i vari sistemi. Anche io, a casa, ho aggiornato il parco macchine con la nuova versione del sistema operativo Apple. Per me programmatore, le innovazioni apportate dalla nuova versione del sistema sono state notevoli. Tanto per dirne una (che da sola vale i 129 Euro per macchina spesi per l’aggiornamento), ho la possibilità di utilizzare il nuovo ambiente di sviluppo Xcode 3.1.1 fornito gratuitamente dalla Apple. Questo ambiente mi consente di utilizzare la versione 4.2 del compilatore gcc e l’innovativa tecnologia di linking LLVM. L’IDE di Xcode 3.1.1 è notevolmente maturata rispetto alla versione 2.5 che veniva utilizzata sul vecchio sistema. E per chi utilizza Xcode come applicazione principale e come motivo quasi esclusivo per utilizzare il computer, vi assicuro che questa è una ragione più che sufficiente per vedere di buon grado l’aggiornamento di sistema. Ora veniamo alla cosa che mi ha fatto riflettere. Per un’utenza ordinaria, quali sono le motivazioni che dovrebbero spingere a fare questo aggiornamento? Mia moglie mi ha detto che il 90% dei giochi che giravano benissimo su Tiger, su Leopard non girano più. Molti altri programmi hanno richiesto il download della versione aggiornata per Leopard. È vero che la versione 10.5 ha aggiunto qualche utile applicazione utente rispetto alla versione 10.4, come “Spaces” o “Front Row”, e qualche altro abbellimento estetico, ma quello che mi chiedo io è: “queste piccole aggiunte da sole sono sufficienti per convincere un utente ordinario a passare alla versione successiva del sistema operativo?”. Sinceramente, a mia moglie, non importa niente se ora il kernel di MacOS X utilizza un nuovo sistema di gestione della memoria, o se tutto il supporto OpenGL è stato ricompilato e linkato con la tecnologia LLVM. A lei non importa se Leopard, rispetto a Tiger, è stato certificato per usi governativi o se sul nuovo sistema si può utilizzare il compilatore gcc-4.2 mentre su Tiger si utilizza il gcc-4.0. Quello che interessa a lei è che i giochi che prima giravano, ora non girano più e che ha dovuto passare un sacco di tempo per andarsi a cercare i vari aggiornamenti dei programmi che utilizzava di più per far girare questi anche sul nuovo sistema. A me questo ha fatto riflettere…

Luca Ciciriello

giovedì 25 settembre 2008

Reinventare la ruota

Come ho scritto nel post precedente, l'OOP non è la panacea per tutti i mali del mondo della programmazione, anzi in alcuni casi gli effetti collaterali di un presunto beneficio sono, sul lungo periodo, devastanti!
È il caso questo del “riutilizzo del software”. Sembra una cosa bella, innocua, decisamente utile, ma è il portone da cui parte la strada verso l'ottundimento della mente creativa dei programmatori.
Mi spiego. Qualche tempo fa mi sono trovato nella necessità di leggere un file XML da programma per estrarne alcuni valori. Era un piccolo file, al massimo una ventina di righe di XML. Allora ho scritto un piccolo parser che identificasse ed estraesse i valori che mi interessavano. In tutto, il parser, consisteva in un centinaio di righe di codice C++ e “pesava” 5 o 6 KB. A questo punto la domanda che è la chiave di questo post. Mi sono sentito chiedere: “Perché non hai usato una libreria già consolidata per leggere il tuo file XML? In Internet puoi trovarne a centinaia”. È vero in Internet posso trovare tutte le librerie e frameworks per leggere un file XML. Istintivamente ho risposto che mi sembrava stupido utilizzare un framework (come ad esempio XERCES) di 30 MB per leggere un piccolo file XML di 20 righe.
Purtroppo questa è la realtà. Il riutilizzo del software porta a questo. Una volta qualcuno ha scritto un qualcosa per risolvere un certo problema, e da allora tutti si è obbligati ad utilizzare quel qualcosa per risolvere anche i nostri di problemi. Il motto del riutilizzo del software è: “è stupido reinventare la ruota tutte le volte”.
Questa sembra essere anche la filosofia di molti prodotti commerciali come ad esempio il C++ Builder della Borland (ok, d'accordo, C++ Builder non è più Borland, ma mi piace ricordarlo come tale). In C++ Builder, esistevano (ed esistono tutt'ora) “componenti” per ogni piccola attività. Volevi aprire un socket? Niente di più semplice. Prendevi il componentino socket e lo trascinavi sulla tua form. Volevi creare un thread? Stessa procedura. Vuoi leggere un file XML? E che problema c'è, il Builder ha il componente giusto per te. In C++ Builder esiste un componente per ogni cosa si voglia fare e senza scrivere una linea di codice. Questo non è più programmare! È solo giocare con dei mattoncini LEGO per costruire il nostro giocattolino. Può sembrare assurdo, ma nella grossa ditta in cui sto prestando servizio come consulente, ho conosciuto delle persone che sono state assunte come programmatori, e che non sono in grado neppure di scrivere le 6 righe di codice in C++ per aprire, leggere e bufferizzare un file testuale. Questo perché lavorano con un ambiente di sviluppo che non gli mette a disposizione il componente adatto per farlo. Come vi ho detto sembra assurdo, ma vi giuro che è la pura verità.
Per fortuna esistono ancora dei programmatori che non hanno paura di sporcarsi le mani a scrivere codice, cavalieri della programmazione che combattono tutti i giorni contro l'ottusità di chi preferisce giocare con il LEGO (non che a giocare con i mattoncini LEGO ci sia qualcosa di male, io lo adoro, ma la programmazione è un'altra cosa).
Per questo tutta la mia stima va a quei programmatori di Google che per scrivere Chrome hanno deciso di reinventare la ruota. Nonostante esistessero letteralmente centinaia di interpreti java-script già esistenti, loro hanno deciso di scriversene uno tutto loro. E, guarda caso, questo funziona meglio ed è decisamente più veloce (dalle due alle tre volte) rispetto a tutti gli interpreti già pronti a essere riutilizzati. Forse reinventare la ruota tutte le volte non è poi così stupido. Forse rimettersi a fare i programmatori ha i suoi vantaggi e risparmiare tempo a tutti i costi riutilizzando software già scritto non paga poi così tanto. Cosa sarebbe l'arte se tutti gli artisti decidessero di non reinventare la ruota tutte le volte? Ovviamente ognuno è libero di rimanere della propria idea e continuare a considerare l'OOP come il vero ed unico passo avanti nell'arte della programmazione. Quindi ognuno è libero di trarre le proprie conclusioni da questo post. L'importante è non utilizzare un componente già pronto per farlo.

Luca Ciciriello

sabato 20 settembre 2008

“Parla, SHRDLU, parla perché possa capirti...”

Sono sicuro che molti di voi avranno riconosciuto in queste parole il titolo di uno dei capitoli del libro di D. R. Hofstadter “Gödel, Escher, Bach: un'eterna ghirlanda brillante”. Il perché io abbia scelto questo titolo lo vedremo fra breve.
La gente è proprio strana. Da quando è stato proposto il paradigma ad oggetti fino ad oggi, un programmatore è praticamente obbligato a scrivere programmi OO.
L'OOP risolve alcuni dei problemi che hanno afflitto il mondo della programmazione come la modularità e la riusabilità del software, ma non è la panacea per tutti i mali del mondo. La programmazione procedurale è tutt'altro che defunta, soprattutto per quanto riguarda il multithreading, e che dire poi della programmazione funzionale? Ed è proprio di programmazione funzionale che parlerò in questo post. Sì, perché se la mia lingua madre è il C++, la mia seconda lingua è sicuramente il LISP e più precisamente quello che è diventato lo standard de-facto del LISP ovvero il Common LISP (CLISP, nell'implementazione GNU che uso io su MacOS X, abbinato a EMACS).
Il LISP è un linguaggio funzionale, cioè tratta simboli invece che valori utilizzando l'eleganza della notazione polacca inversa. Infatti LISP sta per LISt Processing. Inventato nel 1958 da John McCarthy al MIT ed implementato da Steve Russell (famoso per aver creato SpaceWar il primo videogame della storia) su un IBM 704, vanta il primato di essere il più antico linguaggio di programmazione ancora pienamente in uso ai giorni nostri. È il linguaggio usato di preferenza nel campo della ricerca sull'AI e nello studio del linguaggio naturale applicato ai calcolatori. Proprio nel campo della ricerca sul linguaggio naturale troviamo l'ormai leggendario programma SHRDLU scritto dall'altrettanto leggendario maestro Terry Allen Winograd nel 1972 alla Stanford University. A mio avviso (e non solo mio), SHRDLU è uno dei programmi più geniali ed illuminanti nell'intera storia della programmazione. Scritto in “Micro Planner”, un'implementazione LISP del linguaggio PLANNER, racchiude il seme di molte idee riprese poi in seguito in altri sistemi anche commerciali come sistemi operativi e compilatori. Il kernel di SHRDLU (contenuto nel file della versione originale plnr.lisp) rientra nella categoria dei “theorem proover”. SHRDLU è in grado di ricevere i suoi input nel normale inglese parlato, eseguire le azioni che gli vengono chieste e produrre i suoi output sempre in inglese. Quello che fa SHRDLU (qui sto semplificando molto un sistema estremamente più complesso) è esaminare la frase inglese fornita in input attraverso un sistema sintattico-semantico (eta oin) e trasformare questo input in sentenze logiche (teoremi) analizzabili dal theorem proover. Anche se la “conoscenza dell'universo” di SHRDLU è limitata al cosiddetto “mondo dei blocchi”, questo programma rappresenta un passo importantissimo nella reale comprensione di una frase in linguaggio naturale da parte di un sistema informatico. Sicuramente il successo di SHRDLU risiede in gran parte nel fatto di essere scritto in LISP. L'enorme flessibilità di questo linguaggio dà a chi lo usa la libertà di concentrarsi sulle idee e sulla creatività e di tralasciare completamente i dettagli implementativi. Ad esempio, se io voglio creare una lista di oggetti in LISP, semplicemente scrivo: (LIST obj1 obj2 obj3) senza preoccuparmi di che tipo sono obj1, obj2 e obj3. In linguaggi imperativi come il C++ devo prima di tutto definire il tipo degli oggetti che devo inserire nella lista, diciamo che voglio una lista di interi (tipo che deve essere uguale per tutti gli oggetti nella lista), creare il contenitore lista: list<int> myList (ovviamente dopo aver specificato la libreria standard attraverso la dichiarazione dell'header #include <list> e dopo aver specificato che questo oggetto si trova nel namespace std), dichiarare e definire i tre oggetti: int obj1 = 3; int obj2 = 1; int obj3 = 5; inserire questi oggetti nella lista myList creata in precedenza: myList.push_back(obj1); myList.push_back(obj2); myList.push_back(obj3); e solo ora è possibile utilizzare l'oggetto lista.
Quello che però rende i programmi funzionali come il LISP veramente interessanti per la creatività di un programmatore è la capacità “introspettiva”. Un programma LISP può analizzare il suo stesso codice e modificarlo riscrivendone alcune parti, aggiungendo o rimuovendo funzionalità e tutto questo a run-time. Questa è la mia idea di AI, e secondo me è l'unica strada per raggiungerla: programmi che sono in grado di riscriversi da soli adattandosi ai cambiamenti non previsti dell'ambiente operativo in cui girano. Programmi che modificano programmi che modificano programmi. L'AI non può essere concepita da un design rigido fatto a tavolino, ma deve necessariamente essere un processo emergente.
Quest'anno il LISP compie 50 anni e sono sicuro che avrà ancora un ruolo fondamentale nei futuri sviluppi della computer-science, nonostante stiano prendendo piede anche altri linguaggi come il Python (van Rossum, 1980) che unisce assieme le caratteristiche dei linguaggi funzionali con quelle dei linguaggi imperativi.
Staremo a vedere, ma le prospettive sono decisamente interessanti.

Luca Ciciriello

martedì 9 settembre 2008

A systematic process

Parliamo un poco di grandi maestri. In questo blog ho già nominato Bjarne Stroustrup, sicuramente uno dei più grandi. Parliamo qui adesso di Andreas Zeller. Professore ordinario all'Universität des Saarlandes in Saarbrücken, Germania, è grazie a lui se noi oggi parliamo di “breakpoint”, “variable content”, “code completition”, o più in generale di debugger visuali. Infatti, assieme a Dorothea Lütkehaus, nel 1994 ha scritto il primo debugger visuale della storia. Nato come frontend del già famoso GNU gdb, “ddd” è sicuramente una conoscenza ben nota di chi abbia bazzicato un po' nella programmazione UNIX. Cosa molto più importante, Zeller ha definito una teoria scientifica del debugging. Riporto qui di seguito le sue parole tratte da uno dei suoi articoli più famosi (contenuto anche nello splendido libro che ogni programmatore dovrebbe leggere: “Beautiful Code”). Zeller dice:

“Quando i programmatori debuggano un programma, è per ricercare la causa di una failure. Questa può essere originata dal codice, dagli input o dall'ambiente dove il programma gira. Questa causa deve essere trovata ed eliminata. Una volta eliminata la causa, il programma riprenderà a funzionare. Se così non dovesse essere, nonostante aver eliminato la causa, è giunta l'ora di rivedere le nostre credenze sulla causa che ha generato la prima failure. Il processo generale di ricerca delle cause è chiamato 'metodo scientifico'. Questo metodo, applicato alle failures di un programma funziona come segue:

Si osserva la failure del programma.
Ci si costruisce un'ipotesi per la causa di questa failure che sia consistente con l'osservazione.
Si usa questa ipotesi per fare previsioni.
Si testano le previsioni sperimentando e facendo nuove previsioni.
Se la sperimentazione e l'osservazione soddisfano le previsioni, si rifinisce l'ipotesi iniziale, altrimenti bisogna trovare un'ipotesi alternativa.
Bisogna ripetere questo processo finché l’ipotesi iniziale non si possa più raffinare ulteriormente.

A volte le ipotesi possono diventare teorie. Questo vuol dire che abbiamo un 'Framework concettuale' che spiega e predice alcuni aspetti dell'universo. Infatti, il nostro programma malfunzionante può essere un aspetto veramente piccolo dell'universo, ma nonostante questo la nostra teoria ci può dire esattamente dove intervenire per eliminare la causa della failure.
Per ottenere questa teoria, i programmatori applicano il metodo scientifico descritto sopra ripercorrendo all'indietro la catena causa-effetto che origina la failure. Quindi essi osservano la failure (l’output è sbagliato oppure si origina un’eccezione), fanno ipotesi su quale potrebbe essere la causa della failure (la causa potrebbe essere che Y ha un valore errato), fanno una previsione (se Y è sbagliato, il suo valore potrebbe dipendere dalla funzione f() alla linea 632), testano la loro previsione (corretto! Y ha un valore errato alla linea 632), traggono le appropriate conclusioni (questo vuol dire che f() restituisce un valore errato e adesso vediamo perché).
Comunque, al di la di ogni metodo, espediente o trucco, l’uso consistente e disciplinato del metodo scientifico, è la chiave per raggiungere una certa maestria nel debugging. Questo significa tre cose:

Siate espliciti.
Formulate le vostre ipotesi esplicitamente. Scrivetele su un pezzo di carta o spiegate il problema ad un vostro collega. Tenere una traccia scritta del vostro problema vi può permettere di interrompere il lavoro e riprenderlo il giorno dopo a mente fresca.

Siate sistematici.
Siate sempre consapevoli di quello che state facendo. Non investigate e non fate cambiamenti a caso senza avere un’ipotesi chiara ed una previsione certa. Assicuratevi di aver preso in considerazione tutte le possibili cause.

Cercate le cause partendo dalle più probabili.
Il metodo scientifico vi garantirà di trovare la causa della vostra failure, ma non vi dirà quando. Quindi, come prima cosa identificate le possibili cause del vostro problema e concentratevi prima sulle più probabili e su quelle che richiedono lo sforzo minore.

Sfortunatamente, i debugger visuali come li conosciamo oggi, non supportano il metodo scientifico. Sicuramente questi sono strumenti estremamente utili per investigare il codice ed i risultati che produce, ma sono utilizzabili solo da programmatori con grande esperienza, che conoscono come usare sistematicamente un debugger. La mia idea, è quella di insegnare ai programmatori ad usare il metodo sistematico piuttosto che elaborati strumenti di debugging (…e un pochino mi sento colpevole avendo scritto io stesso un elaborato strumento di debugging)”.

Che dire. Dopo le parole di Zeller non mi rimane molto da commentare. Facciamo nostro l’insegnamento del maestro.
Parafrasando il motto di una vecchia pubblicità, posso solo dire: “Meditate, gente. Meditate!”

Luca Ciciriello

venerdì 5 settembre 2008

“There is no worse danger for a teacher than to teach words instead of things”

Siamo tutti concordi nell'affermare che il C++ (assieme all'Haskell, un linguaggio funzionale di ultima generazione, molto elegante) sia uno dei linguaggi più difficili da imparare, e da padroneggiare in maniera decente. Pochi dicono, però che il C++ è anche un linguaggio estremamente difficile da insegnare. Come tutti gli insegnanti di un linguaggio di programmazione, anche io, poco dopo l'inizio di un tipico corso di C++ mi sono trovato al fatidico punto di presentare il classico “Hello World”:

// Program Hello World
// File main.cpp
//
// To build use: g++ -o HelloWorld main.cpp

#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
cout << "Hello, World!" << endl;
return 0;
}

Già presentare un codice “semplice” come questo ad una classe di neofiti presenta delle enormi difficoltà. Da dove partire?
Qui le vie da seguire sono sostanzialmente due. La prima è quella di dire che molti degli oggetti presenti in queste 13 righe di codice verranno introdotti più avanti nel corso. La seconda è quella di spiegare ogni singolo oggetto che compone questo piccolo programma. Supponendo che si sia già spiegato cosa sia un Compilatore ed un Linker, e quale sia la differenza tra un programma interpretato ed uno compilato, bisognerebbe allora partire a spiegare in concetto di Translation Unit per poter introdurre il significato della parola chiave #include (il solo parlare del preprocessore del C++ richiederebbe un corso a parte). Il prossimo passo sarebbe poi quello di spiegare cosa sia una libreria (come quella che contiene iostream) e quindi l'utilità del lavoro svolto dal Linker. Si passerebbe poi a parlare di namespaces, ma per fare questo bisognerebbe parlare anche di “scopo”. A questo punto inizieremmo a fare i primi passi in quel territorio immenso che sono le funzioni per parlare di quella particolare funzione “main” e spiegare cosa sia l'entry point di un programma. Quindi inizieremmo a parlare delle differenze semantiche e sintattiche tra dichiarazione e definizione, tra parametri ed argomenti, quali sono i sistemi di passaggio degli argomenti ad una funzione visti sia dal punto di vista del programmatore sia dal punto di vista del compilatore e quale è l'ordine di lettura usato da una funzione per analizzare i suoi parametri (non vogliamo spendere un po' di tempo a spiegare anche il concetto di variabile?).
Scegliendo questa seconda via dovremmo poi parlare di operatori e di quale sia il significato di overloading, quindi del perché io posso usare lo stesso simbolo grafico per dire al compilatore di eseguire un'operazione di shift a sinistra su una serie di bit oppure di indirizzare un certo dato su uno stream di output. Ma allora a questo punto bisogna anche dire cosa sia uno stream e che ruolo giocano questi oggetti nel sistema I/O del linguaggio C++. Rimane ancora qualcosa da spiegare per far capire ad un neofita il significato di queste 13 linee di codice?
Ogni insegnante segue la sua via. Avendo il tempo necessario, io preferisco sicuramente la seconda. Prendendo come riferimento la frase di Stroustrup del post precedente, io preferisco spendere almeno un paio di giorni di lezione ripercorrendo la storia e le idee che hanno portato allo sviluppo del C++.
Spiego a fondo l'enorme importanza di seguire strettamente lo standard ISO/IEC, perché solo chi conosce a fondo e con maestria lo standard, può trascendere lo standard. Di questi programmatori ne esistono molto pochi e forse veramente solo Stroustrup può permettersi e soprattutto può osare, di trascendere questo standard. Chiunque altro dimostrerebbe solo presunzione (nella migliore delle ipotesi...) o ignoranza del linguaggio (...nella peggiore). Seguire uno standard non è un freno alla creatività, anzi fornisce delle basi solide sulle quali adagiare i pensieri della nostra fantasia. Ricordiamoci che un linguaggio di programmazione, non è solo descrittivo e formale, ma è anche espressivo (quando gli ingegneri inizieranno ad accorgersi di questo?).
Scegliendo la prima delle due vie descritte qui sopra, mi sembrerebbe un po' di correre il rischio paventato nella frase di Marc Block che titola questo post. Una parola può cambiare l'universo se sostenuta dalla verità, ma è solo vuoto se non serve a trasferire o creare significato.

Luca Ciciriello

martedì 26 agosto 2008

“Only a good understanding of the ideas behind the language facilities leads to mastery" (part 2)

Nel 1722 J.S.Bach incomincia a scrivere il primo dei due libri formanti il Das Wohltemperirte Clavier, oder Praeludia, und Fugen durch alle Tone und Semitonia ... con "clavier", raccolta di preludi e fughe che coprono tutte e ventiquattro le tonalità (il secondo libro lo finirà 22 anni dopo nel 1744).
Bach giustifica quest’opera dicendo che l’ha scritta “per l'uso e il godimento di tutti i giovani musicisti desiderosi di imparare […]”. L’ascolto di questi due volumi è qualcosa di straordinariamente appagante. Dà un senso estremo di organizzazione, pulizia e tutto sembra scorrere sostenuto da una qualche forma di armonia con il mondo e con la natura.
Spesso, quando scrivo codice, ascolto questi 24 preludi e fughe. Mi danno un senso di tranquillità e mi aiutano a scrivere un software più “armonico”.
In un software di questo tipo, le istruzioni scorrono via amalgamandosi in un tutto. Il mio maestro mi ripeteva sempre che un codice bello da vedere, è sempre anche quello che funziona meglio, ed io sono pienamente d’accordo con lui. Un codice armonioso, musicale, non pasticciato, si rivela al primo sguardo di un occhio allenato a cercare la bellezza e non soltanto la funzionalità.
Bach era pienamente consapevole di questa ricerca della bellezza, infatti, oltre ad inseguirla nella sua musica, era solito adornare ed abbellire i suoi spartiti con frasi e disegni decorativi.
Per concludere, tornando alla frase di Stroustrup, una filologia di un linguaggio è essenziale per eccellere in quello che è il nostro mezzo di espressione, e solo attraverso la profonda conoscenza di quelle idee che hanno plasmato un linguaggio è possibile apprezzarene pienamente la bellezza. Dopo tutto Keats aveva ragione: “Bellezza è verità, verità è bellezza”…

Luca Ciciriello

domenica 17 agosto 2008

"Only a good understanding of the ideas behind the language facilities leads to mastery" (part 1)

Questa frase di Bjarne Stroustrup, che si trova nell'introduzione alla terza edizione del suo libro “C++ Programming”, ci porta a considerare un argomento che mi sta particolarmente a cuore. Molto spesso, mi è capitato di lavorare con persone che, pur definendosi programmatori, considerano la programmazione solo come un atto ingegneristico. Per loro un programma è soltanto una serie di istruzioni che messe una dietro l'altra fanno sì che bene o male le cose “girino” a dovere. Le parole “stile”, “forma”, “bellezza”, “musicalità”, non hanno alcun significato per loro. È vero, si può scrivere un programma funzionante anche ignorando queste parole, ma (almeno per me) c'è una bella differenza tra scrivere un programma e “comporre” un programma. Ma torniamo alla frase di Stroustrup. Ogni Venerdì, io ho l'abitudine di mandare via mail a tutti i miei colleghi d'ufficio una frase che in maniera più o meno diretta riguarda il mondo informatico. Faccio questo perché spero (speranza regolarmente disillusa) che si inneschi una qualche forma di discussione su argomenti che credo possano interessare a chiunque affermi di essere un programmatore. Bene, quando ho mandato questa frase di Stroustrup ai miei colleghi, uno di loro mi ha detto (testuali parole): “Io sono pagato per scrivere codice e non per diventare un maestro del linguaggio”. Io allora mi chiedo: “chi è quell'atleta che non voglia eccellere nella propria disciplina? Chi è quel musicista che non voglia sempre migliorarsi nella sua arte o quel pittore che ricerchi sempre di avvicinarsi alla perfezione nella tecnica pittorica?”. È tanto diverso per un programmatore? Se faccio della programmazione la mia professione io voglio sapere tutto di questa professione; la storia, chi sono stati i grandi maestri del passato e chi sono i maestri del presente, Tutte le tecniche che mi possano portare ad eccellere in quello che è a tutti gli effetti il mio mezzo di espressione.
Con queste considerazioni concludo la prima parte di questo intervento.
Nella seconda parte vedremo cosa è per me la musicalità di un programma.

Luca Ciciriello

domenica 3 agosto 2008

Così tanta luce in un centimetro quadrato.

Avete mai pensato quanti byte contiene il più piccolo programma compilabile, linkabile e lanciabile scritto in C++ standard?
Bé, non è poi così difficile darsi una risposta. Basta aprire un editor qualsiasi, ad esempio TextEdit (A, tra l'altro, io uso Mac OS X, di Apple. Computers e sistemi per tempi più civilizzati come direbbe Obi-Wan Kenobi), scrivere: int main(){} , contare i caratteri ed accorgersi che sono esattamente 12.
Se ora provate a compilare e linkare la translation unit (che nel frattempo avrete creato salvando il file generato da TextEdit), ad esmpio con gcc, vi accorgerete che nessun errore o warning verrà segnalato.
Avete ora un mini programma completamente eseguibile. Ok, d'accordo, non è che questo programma faccia qualcosa di molto interessante, ma inizialmente ci siamo chiesti quanti byte avrebbe contenuto un programma minimale in C++ e non un programma minimale che facesse qualcosa.
Se ci fermiamo ora a riflettere un attimo, in soli 12 byte c'è tutta l'informazione necessaria ad un compilatore per eseguire un'analisi sintattica e semantica del codice e ad un linker per creare del codice macchina eseguibile.
Parafrasando quindi la frase tratta dall'opera prima di Marisha Pessl : ”Teoria e pratica di ogni cosa”, posso tranquillamente affermare: “Così tanta informazione in 12 byte”.

Luca Ciciriello