lunedì 5 ottobre 2009

"STM"

Inizio con questo post una serie di scritti riguardanti la tecnologia “Software Transactional Memory”. Iniziamo quindi con una descrizione molto “alla larga” che con i prossimi scritti raffinerò.
Come direbbe Simon Peyton-Jones, l'idea di STM non è nuova, ma è una architettura che i “DataBase people” conoscono ed usano già da un sacco di tempo. Infatti il modello transazionale è ben conosciuto nel mondo dei DataBase con le ben conosciute “Begin”, “Commit”, ecc.
Facciamo un esempio. Generalmente, un'azione “act” (che può essere una funzione generica) quando eseguita fa passare lo stato del sistema da stato_1 a stato_2. Nel mondo reale può succedere però che una singola azione sia formata da più sotto-azioni più semplici:

act() {subact_1; subact_2; subact_3; subact_n;}

Cosa succede ora se, ad esempio, subact_3 origina un'eccezione che costringe ad uscire da act()? Succede che lo stato del sistema non è più nello stato stato_1, ma neppure nello stato_2 perché act è terminata prima di portare a termine tutti i suoi compiti pur avendo già eseguito subact_1 e subact_2. In questo caso, abbiamo quindi un sistema che si troverà in uno stato incoerente. Qui entra in gioco la tecnologia STM. Consideriamo ora questo frammento di codice:

act = atomically ( do subact_1 subact_2 subact_3 ... subact_n)

Qui creiamo una transazione attraverso l'applicazione della “funzione” atomically(...). Il sistema si accorgerà del cambio dello stato dovuto alla sequenza di tutte le sotto azioni solo dopo che la “commit” è raggiunta (all'uscita dalla chiamata “atomically”). Se ora, come prima, subact_3 genera la solita eccezione, all'interno della transazione, si ha una rollback e quindi lo stato del sistema continua a rimanere coerente (stato_1).
Vedremo in prossimi post come l'STM diventa la tecnologia chiave nell'evoluzione della programmazione multi-threading.

Luca Ciciriello.

sabato 19 settembre 2009

"Layout"

Fin da ragazzo sono sempre rimasto affascinato leggendo di come le proteine codificassero la loro “funzionalità” sia con le molecole di cui sono composte, sia attraverso la loro forma. Oggi ritrovo questa caratteristica in alcuni linguaggi come Python e Haskell. In questi linguaggi l'informazione è codificata sia dalla sintassi di uno statement, sia dalla sua posizione. Questa feature di un linguaggio di programmazione si chiama “layout”.
In un linguaggio imperativo come il C++ scrivere:

if(num < 0)
….cout << “num is negative” << endl;

oppure:

if(num < 0)
cout << “num is negative” << endl;

è esattamente la stessa cosa per il compilatore (qui ed in seguito ho usato i puntini …. per evidenziare gli spazi di indentazione). Piccolo consiglio: meglio evitare i tab per indentare il codice, usare invece gli spazi. Alcuni editor non reagiscono molto bene ai tab soprattutto per quanto riguarda i linguaggi funzionali.

In Haskell, invece, se scriviamo:

roots a b c =
….let det = sqrt (b*b – 4*a*c)
….….twice_a = 2*a
….in ((-b + det) / twice_a , (-b – det) / twice_a)

il compilatore Haskell (usualmente su MacOS X uso GHC e su Windows uso Hugs) compilerà il tutto senza problemi, ma se scriviamo:

roots a b c =
….let det = sqrt (b*b – 4*a*c)
….twice_a = 2*a
….in ((-b + det) / twice_a , (-b – det) / twice_a)

il compilatore terminerà con un errore che a seconda dell'implementazione dirà che c'è un possibile problema di indentazione.

Il Layout come caratteristica è affascinante perché prima di tutto aggiunge un grado di difficoltà alla codifica di un programma: oltre a scrivere uno statement sintatticamente corretto, bisogna anche stare attenti a dove lo si posiziona. E poi dà una connotazione estetica al codice che si scrive, un'estetica formalizzata dalle regole del linguaggio.

Luca Ciciriello

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