VS85 Team's profileXNALearnersBlogListsGuestbook Tools Help

Blog


    6/23/2008

    Programmazione funzionale in .Net 3.5: tutorial di base su F# (parte III):

    Nota: per poter eseguire questo codice e' necessario Visual Studio 2008 con installato il Microsoft F# Research Compiler (noi abbiamo usato la versione 1.9.1.3).

    Prima di cominciare con il tutorial vero e proprio, e' importante specificare perche' questo linguaggio puo' essere utile. Elenco i vantaggi principali senza entrare nel dettaglio:

    1. questo paradigma e' perfetto per il multithreading, che entro pochi anni diventera' semplicemente irrinunciabile (quattro core? otto core? anche di piu'?)
    2. F# e' strettamente legato a C# e al mondo .Net in generale; si possono scrivere librerie in F# che poi possono essere lette e usate da C# nativamente, nonche' viceversa
    3. F# puo' usare qualsiasi libreria .Net (XNA :) :) )
    4. F# genera codice veloce
    5. F# scrive codice logico/matematico semplicemente fantastico

    In questo ultimo tutorial introduttivo su F# diamo una impressionante (almeno per noi) dimostrazione di potenza espressiva scrivendo un compilatore/interprete di un semplice linguaggio di espressioni aritmetiche. Mostriamo anche come si possono estendere i costrutti funzionali di F# con costrutti object oriented per esportare classi e altri tipi in un progetto .Net scritto in qualche altro linguaggio (in particolare C#).

    Una espressione in questo linguaggio e' definita come

    image

    Le espressioni vengono compilate in listati (liste) di istruzioni in una macchina virtuale basata su stack, dove una istruzione e':

    image

    un tipico esempio di compilazione parte dall'espressione aritmetica originale:

    image

    la converte nel linguaggio aritmetico (dove il numero n sottolineato rappresenta Const(n)):

    image

    la compila nel listato corrispondente:

    image

    e quindi la valuta, ritornando in questo caso il valore 17.

    Per costruire questo tutorial dobbiamo definire dei tipi di dato, o unioni discriminate, per rappresentare istruzioni e le espressioni aritmetiche:

    type expr =
        Add of expr * expr |
        Mul of expr * expr |
        Div of expr * expr |
        Sub of expr * expr |
        Const of int
    

    per cui una variabile di tipo expr sara' in realta' una (e una sola) tra Const(int), Add(e1,e2), ...

    Per trasformare in una lista una espressione possiamo fare pattern matching sul tipo effettivo di una espressione:

    let rec ToString e =
        match e with
            (Add (e1, e2)) -> "(" + (ToString e1) + " + " + (ToString e2) + ")" |
            (Mul (e1, e2)) -> "(" + (ToString e1) + " * " + (ToString e2) + ")" |
            (Div (e1, e2)) -> "(" + (ToString e1) + " / " + (ToString e2) + ")" |
            (Sub (e1, e2)) -> "(" + (ToString e1) + " - " + (ToString e2) + ")" |
            (Const x) -> x.ToString()                    
    

    in cui associamo un pezzo di codice da valutare in caso di ogni forma possibile dell'espressione e. Si potrebbero anche avere patterns piu' complicati, tipo:

    (Add (Mul (e1, e2), Div(e3, e4))) -> ...

    Qui sotto il webcast in cui io e Giulia Costantini mostriamo la stesura di tutto il compilatore/interprete ed infine lo compiliamo in una libreria che apriamo in un progetto C#, per mostrare l'interoperabilita' tra codice F# e codice in altri linguaggi .Net (download video ad alta risoluzione qui):

    [il webcast in streaming richiede silverlight e un attimo di pazienza per il caricamento; in compenso la visione puo' essere effettuata in full-screen a buona risoluzione!]

     

    Programmazione funzionale in .Net 3.5: tutorial di base su F# (parte II)

    Nota: per poter eseguire questo codice e' necessario Visual Studio 2008 con installato il Microsoft F# Research Compiler (noi abbiamo usato la versione 1.9.1.3).

    Prima di cominciare con il tutorial vero e proprio, e' importante specificare perche' questo linguaggio puo' essere utile. Elenco i vantaggi principali senza entrare nel dettaglio:

    1. questo paradigma e' perfetto per il multithreading, che entro pochi anni diventera' semplicemente irrinunciabile (quattro core? otto core? anche di piu'?)
    2. F# e' strettamente legato a C# e al mondo .Net in generale; si possono scrivere librerie in F# che poi possono essere lette e usate da C# nativamente, nonche' viceversa
    3. F# puo' usare qualsiasi libreria .Net (XNA :) :) )
    4. F# genera codice veloce
    5. F# scrive codice logico/matematico semplicemente fantastico

    In questo tutorial vedremo come si manipola la struttura dati che la fa da padrone nel mondo ricorsivo di F#: la lista!

    Dichiariamo tre liste:

    let l1 = [1;2;3;4]
    let l2 = 1::2::3::4::[]
    let l3 = l1@l2

    l1 contiene i primi 4 interi, l2 anche ma e' costruita effettuando una serie di operazioni di tipo "push" a partire dalla lista vuota []. Infine l3 e' la concatenazione delle liste l1 e l2.

    Come primo esempio di una funzione che manipola liste vediamo come sommare tutti gli elementi di una lista con una funzione ricorsiva:

    let rec sum l =
        match l with    
            [] -> 0 |
            x::xs -> x + (sum xs)
    

    in cui vediamo come il pattern matching possa essere usato non solo per ispezionare il valore delle variabili, ma anche la struttura dei loro componenti (ad esempio qui distinguiamo tra la lista vuota [], la somma dei cui elementi e' 0, e la lista con almeno un elemento x che va sommato alla somma del resto della lista).

    Quindi vediamo in opera alcune cosiddette "funzioni di ordine superiore", ossia funzioni che prendano in input anche altre funzioni per applicarle secondo pattern gia' noti. Una delle piu' semplici e' la funzione di riduzione, ossia:

    let rec reduce f l =
        match l with
            [] -> failwith "la lista non puo' essere vuota" |
            [x] -> x |
            x::xs -> 
                let acc = reduce f xs
                in f acc x
    

    in cui la funzione f viene applicata alla riduzione della coda e all'elemento corrente. Se f fosse la funzione che ritorna il massimo tra due valori:

    (fun a -> fun b -> if a > b then a else b)

    allora reduce ritorna il massimo elemento della lista. Se invece come f usiamo la funzione somma:

    (fun a -> fun b -> a + b)

    allora reduce ritorna la somma di tutti gli elementi della lista. Esistono moltissimi altri patterns tipici di iterazione di liste e di collezioni, esattamente gli stessi che si trovano in LINQ (ed ecco perche' abbiamo presentato LINQ come una estensione funzionale di C#, piuttosto che presentarlo come fanno molti come libreria per accesso ai databases).

    Qui sotto il webcast tenuto da me (Giuseppe Maggiore) e da Giulia Costantini in cui produciamo questo codice (e molto altro) spiegando in dettaglio cosa significa e mostrando il risultato della sua esecuzione (download ad alta risoluzione qui):

    [il webcast in streaming richiede silverlight e un attimo di pazienza per il caricamento; in compenso la visione puo' essere effettuata in full-screen a buona risoluzione!]

     

    6/22/2008

    Programmazione funzionale in .Net 3.5: tutorial di base su F# (parte I)

    Nota: per poter eseguire questo codice e' necessario Visual Studio 2008 con installato il Microsoft F# Research Compiler (noi abbiamo usato la versione 1.9.1.3).

    Prima di cominciare con il tutorial vero e proprio, e' importante specificare perche' questo linguaggio puo' essere utile. Elenco i vantaggi principali senza entrare nel dettaglio:

    1. questo paradigma e' perfetto per il multithreading, che entro pochi anni diventera' semplicemente irrinunciabile (quattro core? otto core? anche di piu'?)
    2. F# e' strettamente legato a C# e al mondo .Net in generale; si possono scrivere librerie in F# che poi possono essere lette e usate da C# nativamente, nonche' viceversa
    3. F# puo' usare qualsiasi libreria .Net (XNA :) :) )
    4. F# genera codice veloce
    5. F# scrive codice logico/matematico semplicemente fantastico

    In questo tutorial vedremo come dichiarare funzioni e costanti in F#. Cominciamo con il dichiarare alcune variabili:

    let a = "una stringa"
    let b = 10
    let c = 10.0f
    let d = 'a'
    

    con cui dichiariamo una serie di variabili immutabili (quasi delle costanti), lasciando che sia il compilatore a dedurne il tipo. Usiamo la console F# Interactive (integrata in Visual Studio) per farci compilare il codice al volo ed eseguirne pezzi. Vediamo cosa dice la console FSI quando le diamo in pasto il codice appena visto:

    val a : string
    val b : int
    val c : float32
    val d : char
    

    a e' una stringa, b e' un intero, c un float e d un char.

    Dichiariamo adesso una funzione che somma tre interi tra loro:

    let somma x y z = x + y + z
    

    Rispetto al modo "classico" di scrivere una funzione di somma notiamo soprattutto che non esiste la keyword "return" (e' tutto implicito) e non serve specificare il tipo dei valori da sommare tra loro o il tipo restituito. Rispetto all'equivalente C#, non c'e' paragone quanto a semplicita' e velocita' di scrittura:

    int somma(int x, int y, int z)
    {
        return x + y + z;
    }
    

    vediamo ora un semplice esempio di ricorsione (si noti come non scriviamo solo let, bensi let rec per indicare al compilatore che il nome della funzione verra' usato all'interno della funzione stessa):

    let rec fact n =
        match n with
            0 -> 1 |
            1 -> 1 |
            i -> 
                if i < 0 then failwith "fattoriale: solo numeri positivi"
                else i * (fact (i-1))
    

    in questo snippet vediamo come stiamo facendo cosiddetto "pattern matching" sulla variabile presa in input dalla funzione fattoriale (match n with). Il pattern matching permette di definire una serie di coppie <valore variabile, valore da ritornare> tale che viene ritornato il primo "valore da ritornare" il cui "valore associato" e' pari al valore della variabile. Sembra magico, ma non lo e' :D (ulteriori spiegazioni si trovano nel webcast!).

    Qui sotto trovate il webcast (mio -Giuseppe Maggiore- e di Giulia Costantini) in cui viene estensivamente spiegato e mostrato in azione il codice di questo post (download ad alta risoluzione qui):

    [il webcast in streaming richiede silverlight e un attimo di pazienza per il caricamento; in compenso la visione puo' essere effettuata in full-screen a buona risoluzione!]

     

    6/21/2008

    Programmazione funzionale in .Net 3.5: tutorial di base su LINQ

    In questo webcast vediamo come manipolare collezioni di oggetti in C# tramite LINQ. LINQ e' un modo nuovo per accedere ai dati di liste, array, dizionari e enumerabili in generale con efficienza sia del codice risultante che in termini di tempo di programmazione!

    Usiamo Visual Studio 2008 e C# 3.0, appoggiandoci al framework .Net 3.5.

    Come prima cosa inizializziamo una lista di interi usando la sintassi di inizializzazione delle collezioni:

    List<int> l = new List<int>()
    {
        1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    };
    

    Quindi vediamo come ottenere un risultato molto simile ma senza

    1. dover scrivere due volte List<int>
    2. dover scrivere a mano i valori della lista
    var l = Enumerable
        .Range(0, 10)
        .ToList();
    

    Questa in effetti e' la nostra prima query LINQ, in cui tramite Enumerable.Range(0,10) otteniamo un IEnumerable<int> che quando iterato restituisce gli interi nell'intervallo [0..10]. Da notare che in LINQ la maggior parte delle queries non e' materializzata finche' non la leggiamo, stampiamo o trasformiamo in lista. Quindi Enumerable.Range(0,10) non alloca spazio per undici interi, ma memorizza solo minimo, massimo e valore corrente durante le iterazioni. Invocando ToList() su un enumerabile lo iteriamo e copiamo i suoi elementi su una lista, di fatto

    1. concretizzando la query in memoria
    2. ritornando una lista

    il tipo della variabile l viene dedotto essere List<int> dal compilatore che analizza il valore assegnato alla variabile al momento della compilazione. Si noti che se fosse stato

    var l = Enumerable
        .Range(0, 10);
    

    la variabile l avrebbe avuto tipo IEnumerable<int> e non avrebbe occupato la stessa quantita' di memoria della versione concretizzata su lista.

    A questo punto mostriamo vari modi di stampare la lista l su console. Il primo, piu' banale, e' naturalmente quello di scrivere

    foreach (var i in l)
    {
        System.Console.WriteLine(i);
    }

    dove notiamo l'uso della keyword var per dichiarare la variabile i senza dover specificare che essa e' un intero: d'altra parte il compilatore puo' chiaramente capirlo da solo!

    Un metodo estremamente piu' elegante sarebbe quello di invocare uno dei nuovi metodi disponibili su una lista, come ad esempio il metodo ForEach:

    l.ForEach(
        delegate(int i)
        {
            System.Console.WriteLine(i);
        }
    );

    questo metodo prende come unico parametro una funzione che prende l'elemento corrente della lista, ci fa qualcosa e infine ritorna void. La sintassi della dichiarazione del delegate pero' e' un po' ridondante. In primo luogo il sistema sa che in quel punto del codice passeremo una funzione che prende come parametro un intero, quindi invece di scrivere delegate(int i) basterebbe dire solo i. Poi le parentesi graffe sono inutili, dato che si parla di una sola riga di codice! Infatti, grazie alle nuove espressioni lambda presenti in C# 3.0, possiamo scrivere una versione molto piu' raffinata dello stesso codice:

    l.ForEach(i => System.Console.WriteLine(i));

    dove si intuisce che il delegate passato alla ForEach prende una variabile i, e la trasforma eseguendo una stampa su console. L'idea fondamentale e' che passiamo alla ForEach il lavoro da fare per ogni elemento, e la ForEach invochera' questa nostra funzione su un valore dopo l'altro della nostra lista.

    Passiamo a cose un po' piu' corpose. Ipotizziamo di voler trasformare la nostra lista l in una nuova lista l1 contenente tutti i valori di l trasformati rispetto a una funzione matematica qualsiasi:

    var l1 = l
        .Select(i => - 3 * i * i * i + 6 * i * i + 3)
        .ToList();
    

    vediamo qui in azione la funzione Select, che prende un enumerabile (in questo caso l) e ad ogni elemento di l applica una funzione che lo trasforma, restituendo l'enumerabile con gli elementi trasformati. Si noti che anche qui finche' non invochiamo ToList la collezione risultante resta non eseguita: non occupa memoria ma ogni volta che accederemo ai suoi elementi verra' effettuato un calcolo!

    Un'altra importantissima aggiunta di LINQ al C# 3.0 sono i cosiddetti tipi anonimi. Scrivendo

    var i = new { Item = 0, ItemString = "pippo" };

    dichiariamo la variabile i come avente tipo anonimo. Se infatti poggiamo il mouse sopra la scritta var, Visual Studio fa comparire il tipo della variabile, ossia un tipo 'a (si legge alfa) avente due proprieta' pubbliche, int Item e string ItemString:

    image

    Mettere insieme Select e tipi anonimi produce collezioni complesse e permette di fare cose che prima d'ora richiedevano pagine di codice e di dichiararsi strutture dati ausiliarie solo per contenere piccolissimi tipi di dato temporanei.

    Vediamo ora una ulteriore feature interessantissima che possiamo combinare con LINQ, ossia gli enumerabili infiniti. Creiamo un oggetto di tipo IEnumerable<int> che contenga tutti gli interi:

    IEnumerable<int> NumeriNaturali()
    {
        int n = 0;
        while (true)
        {
            yield return n;
            n++;
        }
    }
    

    naturalmente questo codice non e' il prodotto di una allucinazione folle, ma effettivamente funziona. Certo, se provassimo a materializzare questo enumerable scrivendo:

    var N = NumeriNaturali()
        .ToList();
    

    il programma andrebbe in loop infinito cercando di salvare sulla lista N tutti i numeri prodotti dall'enumerabile ritornato dalla funzione. Ma se ad esempio scrivessimo la seguente query LINQ:

    var l4 = NumeriNaturali()
        .Skip(10)
        .Take(200)
        .ToList();
    

    in questo modo in l4 sono contenuti solo un numero finito di elementi e possiamo tranquillamente convertire l'enumerabile in una lista. Grazie al fatto che in LINQ le queries non vengono eseguite fino al momento in cui cio' e' strettamente indispensabile, e' possibile manipolare oggetti infiniti (generatore di numeri casuali, di perlin noise, di checkpoint di un percorso, di nemici da uno spawning point...) senza ricorrere per questo a memoria infinita (che come ben si sa e' molto costosa :) ).

    Qui sotto ecco un embedding del tutorial in cui mostriamo LINQ in azione in diretta. Il webcast (qui ad alta risoluzione) e' tenuto da me (Giuseppe Maggiore) e da Giulia Costantini in "doppio":

    [il webcast in streaming richiede silverlight e un attimo di pazienza per il caricamento; in compenso la visione puo' essere effettuata in full-screen a buona risoluzione!]