Programmazione.it v6.4
Ciao, per farti riconoscere devi fare il login. Non ti sei ancora iscritto? Che aspetti, registrati adesso!
Info Pubblicità Collabora Autori Sottoscrizioni Preferiti Bozze Scheda personale Privacy Archivio Libri Corsi per principianti Forum
Expert F#
Recensito da Paolo De Nictolis il 30-01-2008 ore 08:50
Copertina ISBN: 1590598504
Autori: Don Syme, Adam Granicz, Antonio Cisternino
Editore: Apress
Lingua: Inglese
Anno: 2007
Pagine: 609 + XXVII
Allegati: Nessuno
Intel Parallel Studio XE 2015
A presentarci F#, il nuovo linguaggio della famiglia .NET che riunisce i paradigmi imperativo, O-O e funzionale, dopo Robert Pickering troviamo stavolta, oltre a Don Syme — il papà del CLR e dei generics .NET oltre che di F# stesso — anche la gradita presenza del Prof. Antonio Cisternino dell'Università di Pisa, il principale maintainer del sito dedicato al libro (creato con SharePoint) dal quale è possibile scaricare il codice di esempio dei capitoli. Non c'è che dire, l'infinita superiorità del framework .NET è in grado di conquistare anche un mondo fortemente ostile alle tecnologie Microsoft come quello accademico.

Il lavoro di review è stato affidato a Tomas Petricek, un MVP del quale vi consiglio caldamente la sua introduzione ad F#. Il testo si divide in due parti: i primi dieci capitoli, con chiaro piglio didattico, illustrano le basi del linguaggio, delle tecniche di programmazione utilizzabili in F# e delle librerie (giova ricordare che una parte di esse deriva direttamente da OCaml, con le opportune differenze); i rimanenti nove mostrano un buon numero di tecniche applicate, dalle Windows Forms allo sviluppo Web, alla programmazione asincrona e concorrente, all'accesso ai dati (senza tralasciare LINQ), all'interoperabilità con COM ed alla creazione di parser ed analizzatori lessicali, per finire con le tecniche di ingegneria del software.

Il primo capitolo ha carattere puramente introduttivo ed illustra la genesi di F#, oltre ai contenuti ed ai destinatari del libro. Nel secondo capitolo facciamo invece la conoscenza con F# Interactive (fsi.exe), una shell per l'interpretazione a linea di comando, che può essere eseguita anche come add-in di Visual Studio; e con il primo programma in F# ed i primi costrutti. Il setup di F# aggiunge anche un'utile voce di menu contestuale Run with F# Interactive per l'esecuzione degli script. Come primo approccio, conviene usare l'istruzione #load per eseguire in F# Interactive gli script di esempio .fsx proposti; il comando accetta come argomento il percorso completo di un file, da specificare come stringa verbatim di .NET, così:
  1. <span style="font-size:1.0em">
  2. #load @"<img src="./nbbc/smileys/angry.gif" width="16" height="16" alt="D:" title="D:" class="bbcode_smiley" />/Things To Do/Not Subject To Postponement/KillGosling.fsx";;</span>

Tornando all'esempio del capitolo, per quanto banale, costituisce l'occasione per introdurre importanti concetti, approfonditi nel seguito, quali il sistema dei tipi di F#, la type inference ed i costruttori di tipo; oltre alle strutture dati, in particolare le tuple, ovvero un certo numero di espressioni, raggruppate insieme per formare una nuova espressione. Nella seconda parte del capitolo, un nuovo esempio ci mostra come fare uso in codice F# di librerie parte del framework .NET, come System.Windows.Forms e System.Net. E' evidente, sin da questo capitolo, l'approccio seguito dal testo: presentare codice di esempio, commentarlo ed approfondire i concetti esposti. Numerosi box sparsi nel testo permettono al Lettore di concentrarsi sull'esempio proposto, per poi tornare eventualmente all'approfondimento teorico.

La caratteristica distintiva di F# rispetto agli altri linguaggi .NET, ovvero il supporto alla programmazione funzionale, costituisce l'oggetto del terzo capitolo. Dopo un'introduzione da manuale universitario ad operatori aritmetici e stringhe, entriamo nel vivo delle caratteristiche funzionali di F# con la trattazione delle liste, per le quali viene presentato uno dei costrutti fondamentali di F#, il pattern matching: fondamentalmente, è un potente strumento per discriminare ed estrarre informazioni dagli elementi di una lista. Una seconda parte del capitolo entra nel vivo della programmazione funzionale, mostrando le operazioni possibili sui function values oltre ad argomenti quali le funzioni anonime ed il basilare pipe-forward operator, un costrutto utile soprattutto perché allevia il lavoro del compilatore nel dedurre l'inferenza di tipo.

La parte finale del capitolo è dedicata ad approfondimenti sul pattern matching e sulla definizione di tipi. Si fa notare, soprattutto, il meraviglioso esempio per elencare ricorsivamente il contenuto di tutti i file in un percorso del disco:
  1. <span style="font-size:1.0em">
  2. #light
  3. open System.IO
  4. let rec allFiles dir =
  5.     Seq.append
  6.         (dir |> Directory.GetFiles)
  7.         (dir |> Directory.GetDirectories |> Seq.map allFiles |> Seq.concat)
  8. </span>
Oltre all'interoperabilità con le librerie .NET, al pipelining, alla ricorsione, all'uso delle funzioni come valore ed all'esecuzione lazy, quest'esempio è soprattutto una pietra miliare della type inference in F#: riuscite a vedere una singola dichiarazione di tipo, nel codice presentato? Rispetto al precedente testo di Pickering troviamo, oltre ad una maggiore chiarezza espositiva, anche un continuo confronto sul come la programmazione funzionale consente di estendere e semplificare i costrutti di quella O-O.

Come più volte ricordato, con F# il framework .NET, il primo ed unico multilinguaggio, diventa oggi multiparadigma, ed il quarto capitolo copre gli aspetti inerenti la programmazione imperativa; non senza aver ricordato, però, i benefici di un paradigma privo di side effects quale quello funzionale: non solo un minor numero di bug e la possibilità di creare programmi compiled on demand, ma soprattutto, la stesura di codice che può essere eseguito as is in maniera concorrente, perché inerentemente thread-safe. Il capitolo comincia presentando i tre tipi di ciclo di F# (for, while e sequenza) ed i mutable, in buona sostanza variabili (o funzioni…) il cui valore può essere assegnato nel corso del programma, insieme con gli array. Un lungo paragrafo è dedicato alle collection .NET contenute in System.Collections.Generic, con il loro uso ed i loro equivalenti in F#. Dopo aver mostrato la gestione delle eccezioni, l'uso base delle librerie .NET di I/O ed i (rari) casi in cui occorre avere a che fare con valori null in F#, il capitolo si chiude mostrando alcune tecniche per evitare side effects pur utilizzando i costrutti imperativi del linguaggio.

I tipi, e gli ineguagliati meccanismi generic e di inferenza di tipo di F#, sono l'oggetto del quinto capitolo. La prima, importantissima caratteristica del linguaggio (solitamente mancante in altri linguaggi che adottano il paradigma funzionale) è l'automatic generalization, ovvero la capacità del compilatore F# di riconoscere, in maniera per l'appunto automatica, che un tipo è generico. Quello che succede nella pratica è che il compilatore F# usa l'automatic generalization quando una definizione di funzione con let o member non forza il tipo delle variabili di input/output. Fatta questa premessa, il testo illustra nel dettaglio le funzioni generic disponibili in F#, terminando con un paragrafo su come usarle nel proprio codice. Passa poi ai tipi del framework .NET, cominciando con la differenza fra value e reference types, per poi dedicare una particolare attenzione al casting. Termina con una sezione di troubleshooting della type inference, con particolare riferimento alla value restriction.

Per quanto, ovviamente, il precedente introduca l'argomento, è nel sesto capitolo che viene trattato il paradigma O-O in F#. Per la verità, in F# molti costrutti dell'OOP, in particolar modo l'ereditarietà, tendono ad avere una minore importanza, grazie alla disponibilità di alternative come i function values, le partial implementations ed il delegation pattern. Ciò nonostante, soprattutto per compatibilità con il passato, F# offre pieno supporto alla programmazione O-O, che il capitolo presenta distinguendo ordinatamente fra le feature per i tipi concreti (classi) e quelle per i tipi astratti (interfacce). Largo spazio viene riservato all'overloading ed alla gestione dello stato, possibile in F#, a differenza che nei linguaggi funzionali cosiddetti puri, tramite i mutable. Il testo parla anche dei module, la keyword di F# per definire classi statiche, ed una tecnica tanto poco usata quanto consigliabile ai novizi, ma a volte utilissima, quale la possibilità di estendere tipi esistenti con nuovi metodi. Apprendiamo, infine, come in compilazione tutti i tipi F# vengono trasformati in tipi standard .NET, e come forzare l'uso di tipi .NET di peculiare uso come struct, delegate ed enum.

Un concetto chiave dell'OOP, l'incapsulamento, è rimandato al successivo settimo capitolo, insieme alle modalità di packaging del codice F#. E' un capitolo, a dirla tutta, che presenta poche peculiarità di F#, giacché gran parte delle caratteristiche presentate sono già disponibili nel framework .NET. I meccanismi utilizzati per l'incapsulamento, tanto per cominciare, sono quelli tipici di .NET, come le definizioni locali ed i livelli di accessibilità; sono invece caratteristici di F# i signature file (file di firma), che definiscono le funzionalità realizzate da un file di implementazione corrispondente. L'uso di namespace e moduli è invece del tutto analogo ai rimanenti linguaggi .NET; anche il compilatore fsc.exe, che viene presentato per la prima volta in questo capitolo con le sue opzioni ed in affiancamento all'interprete a linea di comando, presenta molte analogie con csc.exe. La creazione di assembly, la loro firma e registrazione nella Global Assembly Cache (GAC), e le modalità di packaging delle risorse, installazione (con un interessante cenno a WiX) e deployment delle applicazioni non presentano, anch'esse, alcuna novità per lo sviluppatore .NET.

L'ottavo capitolo ha valore di miscellanea, presentando alcune tecniche comuni di programmazione in F#. Gli argomenti coperti dal capitolo cominciano con la comparazione fra generic, e uso di valori precalcolati e caching per aumentare la velocità di esecuzione. Una parte di estremo interesse, per lo sviluppatore .NET in generale, è la deallocazione delle risorse. Si comincia da quelle per definizione non-managed: handle dei file, connessioni di rete, thread, oggetti della GUI, e più in generale quelle che non hanno a che fare con la memoria allocata su stack ed heap. Ogni oggetto che usa queste risorse dovrebbe implementare l'interfaccia System.IDisposable; oltre a mostrare vari esempi, il testo raccomanda l'uso di uno strumento come CLRProfiler lì dove il garbage collector non arriva: elaborazioni di lunga durata, e callback inattivi. Paradossalmente, però, sono proprio le risorse gestite dal garbage collector, in particolare la memoria sullo stack, a richiedere la maggiore attenzione (gli autori ricordano come è pressoché impossibile il recupero da una StackOverflowException); in questo caso sono d'aiuto due tecniche di capitale importanza in F#, la tail recursion per le funzioni ed il continuation-passing style, utile soprattutto lavorando con gli alberi. Un paragrafo sulla gestione degli eventi, che in F# possono essere usati a guisa di ogni altro valore, chiude il capitolo.

Già Robert Pickering ci aveva parlato nel suo testo delle feature di meta-programmazione che costituiscono, a tutti gli effetti, un quarto paradigma implementato da F#, la Language-Oriented Programming. Con un doveroso omaggio, gli autori riprendono la sua intuizione nel nono capitolo, mostrandoci come F# è atto a trattare le rappresentazioni concreta, astratta e computazionale di un linguaggio. Come esempio di rappresentazione concreta di un linguaggio si parte da System.Xml, per mostrare poi quant'è facile lavorare in F# con una rappresentazione astratta come gli AST (Abstract Syntax Trees). L'estensione dei pattern matching dalle semplici funzioni valore ai tipi dà luogo ai cosiddetti active pattern, mentre la generalizzazione delle sequence expression porta alle computation expression, impropriamente chiamate workflow. Di queste ultime, gli autori ci mostrano analogie e differenze con le Haskell monad. Come esempio ci viene mostrata un'espressione in grado di lavorare su distribuzioni di interi, riprendendo un esempio tratto dal paper “A Programming Language for Probabilistic Computation”.

Le capacità di astrazione di F# raggiungono i loro vertici (attuali) con la reflection e le quotation. Se la prima è un'estensione delle ben note capacità del framework .NET di recuperare a runtime informazioni su assembly, definizioni di tipi e firme dei relativi metodi e proprietà, le quotation sono l'ennesima potenza delle lambda expression già disponibili in C# 3.0, e vengono usate per ottenere una rappresentazione delle espressioni F# sotto forma di AST. Gli esempi mostrati, fra i quali un reader di 50 linee di codice in grado di generare una collezione di oggetti da un semplice CSV, sono impressionanti; ma le quotation si sono spinte ben oltre, fino a poter usare F# come generatore di linguaggi: meta-programmazione SQL con LINQ, piuttosto che la trasformazione in JavaScript operata dagli F# Web Tools descritti in “Ajax-style client/server programming with F#”. Vedremo entrambi nel seguito del testo; per ora, basti sapere che il solito Petricek ha reso disponibile un F# quotations visualizer per dare una rappresentazione visuale alle quotation tramite WinForms.

Coerentemente con la vocazione multiparadigma di F#, il decimo capitolo, nominalmente dedicato alle librerie di .NET ed F#, funge al tempo stesso da riepilogo, da miscellanea e da introduzione. Riassume infatti ordinatamente le librerie presentate nei capitoli precedenti, ne tratta brevemente di nuove, e funge da ponte verso le rimanenti 300 e passa pagine del testo, dedicate alle applicazioni pratiche, che sfruttano tali librerie. Dalle tabelle di overview iniziali scopriamo anche cosa non viene trattato nel testo; si tratta di namespace importanti come Microsoft.Win32, System.Security, System.CodeDom, System.Globalization ed il nuovissimo System.Media. Il capitolo completa poi la trattazione dei precedenti in materia di stringhe, dedicando particolare attenzione al namespace System.Text.RegularExpressions, e dedica un paio di pagine ad alcune strutture dati avanzate.

Il capitolo termina presentando le funzionalità di calcolo numerico offerte da Microsoft.FSharp.Math e dando un'introduzione più organica al namespace Microsoft.FSharp.Reflection. In chiusura, gli autori ci tengono a segnalare alcune librerie che per ragioni di spazio non hanno trovato sistemazione nel testo, ma non per questo meno importanti: Robotics Studio, XNA, GTK#, Extreme Optimization, Irrlicht, WPF (ma all'uso di WPF da F# è dedicato un intero blog) e WCF.

La flessibilità di F# e la sua interazione con le librerie .NET fanno sì che esso possa essere facilmente utilizzato per creare applicazioni grafiche WinForms, oggetto dell'undicesimo capitolo. Per quanto F# Interactive offra un'opportunità unica di vedere una WinForm costruita pezzo per pezzo, a causa dei lunghi listati l'uso di fsc.exe diventa predominante per vedere all'opera gli esempi proposti nel codice associato al testo. Dopo i primi, semplici esempi, che però permettono agli autori di illustrare argomenti quali la creazione ed il layout dei controlli piuttosto che il device context, il testo ci mostra due interessanti esempi, con elementi facilmente adattabili alla programmazione reale. In primo luogo, il pattern Model-View-Controller (MVC) è alla base di un simulatore di plotter di dati acquisiti da sensori, che ci permette anche di apprezzare l'interessante controllo PropertyGrid, che fa uso della reflection per recuperare a runtime le proprietà di un oggetto e modificarle in maniera visuale. Uno degli esempi più classici e belli della matematica, l'insieme di Mandelbrot, fa invece uso della programmazione multithread (approfondita in un capitolo successivo) per gestire i complessi calcoli necessari alla visualizzazione.

Gli argomenti del nono capitolo, in particolare gli AST e le discriminated union, vengono ripresi nel dodicesimo capitolo, che tratta un argomento assai congeniale alle capacità di astrazione fornite da F#: le rappresentazioni simboliche. Vengono presentati due classici esempi: un valutatore di espressioni (sul modello di Matlab e Mathematica) ed un verificatore di circuiti hardware basato sulla logica preposizionale. Il primo esempio usa un analizzatore lessicale ed un parser generati da due tool forniti dal setup di F#, fslex.exe ed fsyacc.exe, che sono oggetto essi stessi di un successivo capitolo. Anche la rappresentazione grafica mediante WinForms, con le strategie scelte per visualizzare opportunamente frazioni ed esponenti, merita un'attenta lettura.

Il secondo esempio è una delle aree di applicazione più consolidate per la programmazione funzionale, e si basa sulla rappresentazione canonica delle preposizioni logiche mediante Bynary Decision Diagram (BDD), oltre che su un precedente verificatore automatico di teoremi, HOL88. Viene dapprima mostrato come le tradizionali tabelle di verità sono efficaci, nel senso che permettono di valutare correttamente le proprietà dei circuiti, ma al tempo stesso sono inefficienti: la verifica di un semplice circuito comincia ad essere pesante, in termini computazionali, già solo con cinque ingressi. I BDD, risalenti ai tardi anni Ottanta, sono invece dei verificatori efficienti, basati solamente su condizionali if…then…else, che usano tecniche di precalcolo e memorizzazione dei risultati intermedi. I risultati mostrati nel testo sono impressionanti: un problema di verifica con 74 variabili booleane, che con le tabelle di verità richiederebbe di effettuare 2 elevato alla 74 casi di test, con i BDD viene portato a termine in pochi secondi.

Il tredicesimo capitolo parla di una delle caratteristiche più interessanti, non solo di F#, ma dell'intero framework .NET: la programmazione concorrente. Dopo una breve introduzione alla terminologia, viene illustrato l'uso della classe System.ComponentModel.BackgroundWorker per la programmazione concorrente sincrona, e come incapsularla. In materia di programmazione concorrente asincrona, F# offre un potente strumento negli asynchronous workflow, costruiti intorno alla classe
  1. <span style="font-size:1.0em">Microsoft.FSharp.Control.Async</span>
. La necessaria gestione dei thread viene enormemente facilitata dalla classe System.Threading.ThreadPool, che fornisce out-of-the-box la propagazione delle eccezioni e la cancellazione dei thread, e permette una gestione del ciclo di vita delle risorse mediante il semplice utilizzo della parola chiave use. Il testo mostra come creare un'applicazione asincrona per il processing in parallelo di un elevato numero di immagini, con una panoramica delle API .NET per la gestione concorrente degli stream di I/O, delle richieste Web, dell'accesso a database ed XML, ed approfondendo l'uso di alcune primitive di Async.

Si passa poi ad esaminare la programmazione concorrente mediante scambio di messaggi, con un esempio di crawler Web. Infine, a dispetto delle classi di alto livello di così semplice ed affidabile uso del framework .NET, passa ad illustrare le primitive di basso livello per la gestione della concorrenza shared-memory, che è necessario (e difficile) gestire quando si vogliono ottenere particolari miglioramenti nelle prestazioni. Il .NET Memory Model ed argomenti come i lock e l'evitamento delle race condition, i mutex ed i semafori sono il filo conduttore di questa panoramica del namespace System.Threading, che termina illustrando l'uso di una classe di stupefacente utilità quando, com'è comune ad una struttura dati, si deve accedere in maniera concorrente ad oggetti che vengono scritti una volta sola, e letti più volte nel corso dell'esecuzione: la classe ReaderWriterLock.

Un linguaggio versatile come F# offre insospettate feature per la programmazione Web: esse sono l'argomento del quattordicesimo capitolo. L'uso dei socket .NET permette di scrivere, sin dal primo paragrafo, un semplice ma completo Web server con settanta-linee di codice-settanta compresi i commenti; il server gestisce direttamente tramite richieste HTTP, di cui effettua il parsing con le espressioni regolari, ogni tipo di contenuto statico (grazie ad un dictionary estensibile dei tipi MIME) ed opera in modalità asincrona. Parte della ricchezza di questo testo consiste nel fatto che parla di F#, ma permette di approfondire la conoscenza di quasi tutti i principali aspetti del framework .NET: i successivi paragrafi sono una ricapitolazione della tecnologia ASP.NET. In verità, buona parte del capitolo è basata sul lavoro svolto da Petricek con l'F# CodeDOM, usato per far generare codice F# alle pagine ASP.NET.

Nulla manca, dalla configurazione del web server tramite il web.config al code-behind, dai server control (comprese naturalmente le data grid, e con un cenno particolare per i validator control) agli User Controls .ascx, dall'accesso ai dati (con un accenno a LINQ, oggetto del successivo capitolo) alle direttive di pagina, dai Custom Control al debugging, dal modello degli eventi a quello dei provider.

L'onda lunga di AJAX non ha risparmiato nemmeno F#, con la nascita ad opera del solito Petricek del progetto collaborativo F# Web Tools; allo stato, questo progetto usa le feature language-oriented di F#, in particolare le quotation, per generare dinamicamente codice JavaScript, con l'obiettivo di migrare al più presto a Silverlight ed al Dynamic Language Runtime. Il testo dedica solo una breve pagina al progetto, che era ai suoi esordi nel momento in cui è stato scritto; in compenso, chiude in bellezza con l'uso da F# dei Web Services, referenziando nello specifico quelli di TerraServer e WebserviceX.NET, e mostrando la loro invocazione asincrona: un task in generale enormemente facilitato dall'uso di Visual Studio, che all'atto della referenziazione di un Web Service genera sia proxy sincroni che asincroni per esso.

Il quindicesimo capitolo è una disamina di tutte le tipologie di accesso ai dati possibili da F#, cominciando dai costrutti per le query di strutture dati direttamente in memoria. I metodi del tipo F# Seq riflettono invero appieno l'approccio di LINQ: interrogazione e manipolare strutture dati direttamente in memoria, utilizzando una sintassi SQL-like che liberi completamente il programmatore dalla necessità di imparare un secondo linguaggio quando deve operare con l'accesso ai dati. La versatilità di questo approccio, applicabile ad una sorgente dati la più generica possibile, fa sì che sia possibile ottenere praticamente gratis il parallelismo: è quello che è stato fatto, in effetti, con PLINQ. La nascita di LINQ porterà quindi ad un nuovo paradigma nell'accesso e nella manipolazione dei dati, ma non farà certo venire meno la necessità della loro persistenza: il capitolo non trascura quindi l'interazione con basi di dati relazionali offerta da ADO.NET, offrendo persino una breve panoramica del modello relazionale, probabilmente ridondante per molti Lettori.

Anche in questo caso, l'argomento è alquanto vasto, ma il testo fornisce un'ottima panoramica, dalle connessioni ai DataSet ed ai DataAdapter, senza trascurare le DataGrid, le stored procedure o la generazione di DataSet tipizzati con xsd.exe. Non poteva mancare, naturalmente, un paragrafo dedicato alle straordinarie funzionalità di Visual Studio per lavorare in maniera visuale su un qualsiasi database per il quale sia disponibile un provider ADO.NET. Tuttavia, viviamo in un mondo in cui LINQ è in mezzo a noi, quindi è il momento di pensare al passaggio ad un ORM del calibro di LINQ to SQL (per quanto, naturalmente, lo strato più basso di questo ORM faccia uso di ADO.NET).

Il cuore delle funzionalità ORM offerte da LINQ to SQL è il tool a linea di comando SqlMetal, in grado di generare una classe, ereditata da DataContext (la classe principale del namespace System.Data.Linq), che rappresenta l'intero database, sulla quale è poi possibile lavorare con l'O/R Designer di Visual Studio. L'uso delle quotation di F# fornisce una sintassi ancor più compatta e naturale per l'interrogazione del modello ad oggetti. A dispetto delle straordinarie funzionalità di ORM, come ricordavo in apertura LINQ è stato pensato come modello di accesso unificato alle sorgenti dati le più disparate possibili: il capitolo termina mostrandocene per l'appunto la versione per manipolare dati in formato XML, LINQ to XML, una rivoluzione copernicana rispetto al classico XML DOM che non solo permette di costruire documenti XML in modalità funzionale, ma anche di interrogarli con le query LINQ, così simili alla sintassi SQL, invece che tramite XPath.

Paradossalmente, a parte LINQ, l'aspetto più noto di F# sono le astrazioni sui tipi e le capacità di manipolazione del codice a runtime che ha donato a C# 3.0. Un linguaggio come F# si presta infatti straordinariamente alle operazioni proprie della teoria dei compilatori; in particolare, all'analisi lessicale ed al parsing di espressioni, oggetto del sedicesimo capitolo che approfondisce l'uso dei tool fslex.exe ed fsyacc.exe precedentemente incontrati. Dopo una breve introduzione al parsing di semplici file su linee separate con System.Text.RegularExpressions, il capitolo affronta separatamente l'analisi lessicale ed il parsing. Si parte con la tokenizzazione tramite FsLex, affrontando l'uso del tipo Microsoft.FSharp.Tools.FsLex.LexBuffer ed i pattern per la creazione di regole.

Il parsing recursive-descent ed un box di introduzione alle grammatiche context-free ed alla Backus-Naur Form (BNF) fungono da ponte per il parser LALR implementato da FsYacc. Come esempio viene mostrato un semplice, ma completo linguaggio BASIC-like, Kitty, inventato dagli autori; i relativi paragrafi affrontano anche la risoluzione dei due principali tipi di conflitto per una grammatica (reduce-reduce e shift-reduce), la precedenza degli operatori e l'associatività, ed introducono l'utilissimo switch
  1. <span style="font-size:1.0em">-v</span>
di fsyacc.exe, che permette di produrre un estratto leggibile degli stati del parser. Il capitolo si chiude con l'uso dei cosiddetti combinatori per il parsing di dati binari.

Per quanto .NET ed F# siano il futuro (ed il presente), l'eredità di C e COM è ancora ben viva nel mondo dello sviluppo. Il diciassettesimo capitolo, dedicato all'interoperabilità fra F# e codice unmanaged, è comunque uno dei più interessanti e piacevoli del testo, un avvincente tuffo nel passato (e nel presente) che ci ricorda dov'era il 90% del mercato di ieri, per farci capire dove sarà il 90% del mercato di domani. La succinta introduzione iniziale ai meccanismi del CLR ed alla gestione della memoria a runtime, un argomento di assoluta importanza per l'interoperabilità fra differenti linguaggi di programmazione, è impagabile, e degna del Richter. La parte sulla COM Interoperability inizia anch'essa con un'introduzione al modello per componenti che per un decennio ed oltre ha fatto la storia della programmazione Microsoft; ricordare IDispatch o IUnknown, AddRef o QueryInterface, le OLE o le type library riporta ad un'epoca gloriosa, in cui il computer lasciava i data center per arrivare sulle scrivanie dell'umanità.

Per quanto quel modello sia stato sorpassato dai tempi, alcune sue geniali caratteristiche li precorrevano, e sono state alcune delle basi da cui è nato il CLR: l'interoperabilità binaria, fondamento della natura multilinguaggio di .NET; il caricamento dinamico dei componenti; la reflection, nata soprattutto per gli ambienti di scripting ed oggi portata ai confini della programmazione conosciuta. Dopo un box sull'uso del registro come store per i metadati COM, è tempo di passare ai due tipi di wrapper, che permettono di incapsulare un mondo all'altro: il COM Callable Wrapper (CCW) da un lato, il Runtime Callable Wrapper (RCW) dall'altro. Con impareggiabile simmetria, due coppie di tool del .NET Framework permettono di generare automaticamente questi wrapper: da un lato, tlbimp.exe porta i componenti COM (data la loro type library lì dove lo sviluppo non era mai giunto prima, coadiuvato da aximp.exe per gli ActiveX; dall'altro, il vertice che nessuno può eguagliare ben si presta a ridursi in forma di type library grazie a tlbexp.exe, o a regasm.exe che in più effettua anche la registrazione di un assembly come componente COM.

Il testo mostra come usare da codice F# in tal guisa il controllo ActiveX che implementa il Flash Player, e come esporre invece come componente COM un semplice assembly F#. Per quanto molto sia possibile con i quattro tool citati, in alcuni casi, come nella specifica del ProgID, il namespace System.Runtime.InteropServices con le sue annotation (un'idea copiata, ma non eguagliata) fornisce un impareggiabile aiuto. L'interoperabilità è materia complessa, ma chi programma insieme al 90% dell'umanità non è mai solo: la Microsoft fornisce le versioni .NET managed dei componenti di Office, delle librerie DirectX e del Web Browser control.

Un altro lato dell'interoperabilità .NET è più complesso, ma ancor più affascinante. La COM Interoperability, con le sue facility, è limitata al mondo di Windows ed all'implementazione Microsoft dello standard ISO CLI (Common Language Infrastructure). In altri tempi, voci interessate e nemiche di un'umanità oggi in grado di usare un computer hanno cercato di far credere che .NET fosse limitato alle piattaforme Microsoft; come se il concetto stesso di limite, possa essere applicato alla piattaforma .NET. È stata invece Microsoft stessa a porre le basi per il .NET multi-piattaforma, includendo un meccanismo standard di interoperabilità disponibile per tutte le implementazioni del CLI, come Mono (o come Rotor, ancora una volta uno sforzo Microsoft): sto parlando, naturalmente, del Platform Invoke. Il modello di P/Invoke (com'è familiarmente chiamato) prevede il caricamento a runtime di librerie nello spazio di lavoro del programma, permettendo al codice managed di invocare le funzioni esportate da tali librerie.

P/Invoke permette di ottenere tramite una semplice chiamata di sistema l'indirizzo della funzione in memoria, ma rimane in carico al programmatore la convenzione di chiamata (il testo dedica molta attenzione a questo argomento, distinguendo in particolare fra stdcall e cdecl) e, soprattutto, il marshalling dei parametri. Gli autori approfondiscono una serie di argomenti, dalle semplici chiamate extern al layout in memoria delle strutture dati, dal marshalling dei parametri (con una particolare attenzione per il tipo stringa) ai puntatori a funzione, terminando con un esempio, sufficientemente completo, di memory mapping per l'accesso paginato in memoria di file. L'accento è soprattutto sulla natura dichiarativa di P/Invoke, basata sulle annotation, che tanto semplifica il lavoro allo sviluppatore; ma anche sulla necessità, quando si lavora con P/Invoke, di operare con privilegi elevati, e pertanto di ridurne al minimo l'uso e di concentrarlo in un numero di assembly verificabili che fungono da gateway verso il CLR.

Gli ultimi due capitoli del testo sono pronti ad affrontare i paradigmi dell'ingegneria del software con F#: il primo, il diciottesimo capitolo, ci parla di debugging e testing (in particolar modo col framework NUnit). Prima di NUnit, però, il capitolo affronta il debugging con il debugger integrato in Visual Studio, ed usando roughly F# Interactive. Il debugger di Visual Studio è uno strumento straordinario (nella versione 2005 e successive è perfino in grado di debuggare applicazioni concorrenti), ed il testo ne evidenzia in particolare i conditional breakpoint e la possibilità di attaccarsi ad un processo in esecuzione. Il CLR a sua volta offre un buon numero di servizi di debugging, quali il tracing dello stack piuttosto che contatori di prestazioni sulle risorse, gestione dei processi del sistema operativo o accesso al log degli eventi, accessibili tramite le classi del namespace System.Diagnostics. Il testo si concentra soprattutto sulle classi relate al debugging, mostrando il meccanismo degli Assert e la gestione del debugger.

La parte su F# Interactive è piuttosto breve e si concentra sulle direttive e sui metodi messi a disposizione, oltre che sulle modalità di compilazione del codice F#: va detto, infatti, che per quanto F# Interactive appaia all'utente come un interprete, è in realtà un compilatore: entra in azione appena la type inference fornisce informazioni sufficienti per la risoluzione dei tipi di un frammento di codice, lo compila al volo in un assembly generato dinamicamente e lo esegue. Viene infine mostrato l'uso di NUnit e, più in generale, del Test-Driven Development (TDD) per identificare un bug in una semplice funzione di esempio; questa offre tuttavia l'occasione per parlare degli attributi aggiuntivi offerti dal framework per supportare la documentazione e la classificazione dei casi di test. Vengono solo accennata la presenza di altri strumenti di unit testing disponibili per .NET, quali Visual Studio Team System ed NCover.

Il diciannovesimo ed ultimo capitolo illustra le opinioni degli Autori in merito alla progettazione di librerie F#. Un primo approccio può essere quello di tenere conto della posizione di F# all'interno del framework .NET e progettare dunque librerie, cosiddette vanilla, completamente aderenti alle .NET Library Design Guidelines. In tal caso, occorre riservare i costrutti propri di F#, come le discriminated union o i function type, all'implementazione interna, evitandone l'uso nelle API pubbliche, e sottoporre le proprie interfacce all'esame di FxCop. Un secondo approccio è, naturalmente, quello di applicare il paradigma funzionale anche alle metodologie di progettazione, utilizzando le proprietà di composizione del paradigma che portano a librerie in cui prima vengono definiti i tipi, e poi le funzioni che operano su di essi. Il testo riprende infine la creazione di librerie .NET-compliant, fornendo una lunga serie di raccomandazioni.
proAbbiamo di fronte a noi, non solo il testo di riferimento su F#, un estensivo approfondimento degli argomenti trattati nel Pickering (che rimane un magnifico testo introduttivo a carattere pratico), ma anche una splendida occasione per imparare i fondamenti (e qualcosa in più) di gran parte delle librerie .NET, dalla viva voce di uno dei protagonisti del framework. La divisione in due parti, quella didattica e quella dedicata alle applicazioni pratiche, permette di soddisfare ogni tipologia di lettore. Questo testo è un'occasione ad oggi unica per imparare il linguaggio che nel medio-lungo periodo sostituirà C# al vertice della programmazione.
controL'unico contro di questo testo è che non esistono (inspiegabilmente) disposizioni legislative, esecutive e giudiziarie che ne rendano la lettura obbligatoria a chiunque voglia essere appellato col nome di sviluppatore.
Precedente: Maria, un nuovo storage engine per MySQL
Successiva: Un tutorial sui microformati
Intervento di Jacopo Cocchi a.k.a. diobrando del 30-01-2008 ore 16:40, Udine (UD)
Barone
Barone
(260 interventi)
Iscritto il 18-09-2004
Io francamente mi chiedo come facciate a fare recensioni così accurate a scadenza praticamente settimanale senza contare tutto il resto.

Faccio un sincero plauso a te e a tutti gli altri articolisti/recensionisti.
Per quanto opinabili possano essere, vanno nello specifico sempre e cmq non come le solite recensioni patinate che si leggono ad es. nei negozi online.


Complimenti davvero :)
Copyright Programmazione.it™ 1999-2014. Alcuni diritti riservati. Testata giornalistica iscritta col n. 569 presso il Tribunale di Milano in data 14/10/2002. Pagina generata in 0.432 secondi.