venerdì 28 dicembre 2012

Sviluppare codice migliore con la programmazione funzionale


Minimizzare gli impatti del cambiamento dovrebbe essere uno degli obiettivi prioritari nella produzione del software, soprattutto se in contesti di medio-alta complessità. Normalmente, infatti, si assiste al fenomeno dell'ingessatura del codice: nessuno si azzarda a introdurre cambiamenti migliorativi perché questo potrebbe portare a conseguenze inaspettate, potenzialmente drammatiche.

Per minimizzare gli impatti del cambiamento esistono strumenti e tecniche diverse: ad esempio il test driven development, oppure il defensive programming. Un altro metodo è quello di scrivere codice secondo il paradigma della programmazione funzionale (FP = Functional Programming).




Un programma scritto in un linguaggio FP puro è normalmente esente da "side-effect": cioè è un software che si comporta in un solo modo, predicibile, e che ha un comportamento identico una volta compilato. Questo perché si traduce sostanzialmente in una funzione che prende in input una qualunque serie di funzioni che agiscono su costanti. E' come un meccanismo di ruote e pulegge: una volta in movimento, si comporta sempre nello stesso identico modo.

Purtroppo solo una piccolissima parte di programmi può essere scritta in FP pura. Questo perché un programma per essere utile deve poter prevedere: 1) input dall'esterno e 2) mantenimento di uno stato. Queste però sono due cose che sono "inesprimibili" in FP puro.

Quello che possiamo fare è invece utilizzare un linguaggio ibrido, che permetta cioè sia la programmazione imperativa (magari ad oggetti) cui siamo abituati, sia la programmazione funzionale, cercando di minimizzare e confinare la parte imperativa in un punto noto - e ridotto - del codice sorgente. Eccellenti esempi di linguaggi ibridi sono oggi Scala e C#.

In Scala, le variabili vengono nominate in due modi diversi: var, la variabile "normale" cui siamo abituati, e val, una variabile che, una volta che abbia assegnato un valore, non lo modificherà mai per tutta la sua vita. (In C# le variabili val di Scala sono scritte come readonly) Un programma scritto in Scala che abbia molte var, è un programma che segue la logica imperativa, che crea oggetti che mantengono uno stato e che quindi è potenzialmente pieno di side-effects. Viceversa, un programma che abbia pochissime o nessuna var, e val al loro posto, è un programma scritto in logica funzionale, e che avrà pochi o nessun side-effect. Questo secondo tipo di programma è molto più facile da mantenere, dovrà essere testato di meno, e quindi avrà maggiore valore.

Come si possono scrivere normali costrutti in logica funzionale? E' difficile, perché il nostro cervello è abituato a pensare in termini di "oggetti" e di "stato", e molto poco in termini di "funzioni" e "funzioni di funzioni". Però con un po' di pratica l'obiettivo può essere centrato.

Vediamo un esempio. Supponiamo di voler scrivere una classe che prende in input una stringa e ne calcola il suo valore ASCII (il valore ASCII di una stringa è la somma dei suoi caratteri intesi secondo la codifica ASCII).


class TestClass(givenString: String) {

    require(givenString != null)
    private var gStr: String = givenString


    def getValue() = {
        this.compute()
    }

    private def compute(): Int = {
        var tempSum: Int = 0
        for (charV <this.gStr.toCharArray()) {
            tempSum += charV.toInt
        }
        tempSum
    }

}


Questo programma in Scala contiene due var: una per mantenere lo "stato", che è la variabile che contiene la stringa da trasformare in numero, e una per mantenere il numero associato alla stringa mentre viene calcolato, all'interno di un ciclo for.

Come trasformare questa classe in logica funzionale?

Dobbiamo come prima cosa far sparire lo "stato" dalla classe. La nostra classe deve comportarsi come una "funzione" e quindi non deve mantenere in memoria parametri che possono cambiare valore nel tempo. Farlo è molto semplice: invece che memorizzare givenString come variabile (var) lo memorizzeremo come costante (val). Oppure, potremmo non memorizzarlo del tutto e passarlo così com'è al metodo di calcolo compute().

Più difficile è invece sostituire il costrutto for che calcola il valore. Qui useremo invece una formula classica della FP. Creeremo un metodo che ritorna una collezione, cioè un array dinamico, di valori ASCII, ognuno per ciascun carattere che forma la stringa. Poi su questa collezione applicheremo una funzione di trasformazione: nel nostro caso "sum". Ogni collezione fornisce metodi operatore sui valori che la compongono, e in più può accettarne di nuovi appositamente scritti.

Ecco il nostro codice trasformato in logica funzionale:

class ImmutableTestClass(givenString: String) {

    require(givenString != null)
    private val gStr: String = givenString

    def getValue() = {
        this.compute(this.gStr)
    }

    private def listOfValues(valStr: String) = {
        for (j <- 0 until valStr.length()) yield {
            valStr.charAt(j).toInt
        }
    }

    // 1. Convert the sequence to actual collection
    // 2. Add "mapping" to collection, example: .sum, .mkString
    private def compute(valStr: String): Int = {
        val listOfVals: List[Int] = this.listOfValues(valStr).toList
        listOfVals.sum
    }

}


Abbiamo buttato via le var e le abbiamo sostituite con val. Il codice è ora puramente funzionale. (E, tra parentesi, funzionerà molto meglio in contesti multi-threading).

Naturalmente lo stesso risultato si può ottenere con C#:

class Immutable
{
    private readonly String tString;

    public Immutable(String testString)
    {
        this.tString = testString;
    }

    public int GetSum()
    {
        return this.CharList().Sum();
    }

    private IEnumerable<int> CharList()
    {
        foreach (char ch in this.tString)
        {
            yield return (int)ch;
        }
    }
}

sabato 22 dicembre 2012

Masterizzare e montare una ISO su Windows 8

Windows 8 supporta nativamente il formato ISO, cosicché è possibile sia montare un'immagine ISO come drive virtuale, sia masterizzarla su CD/DVD/BluRay.

ATTENZIONE: se si è installato un software che gestisce i file .ISO (ad esempio WinZip o Demon Tools), le opzioni per la gestione dei file ISO non sono più visibili da menù contestuale. Occorre modificare le impostazioni dei software di cui sopra, per togliere l'associazione con i file .ISO.

PER MONTARE UN FILE .ISO

Clickare con il tasto destro del mouse sul file .ISO e selezionare "Monta". Il nuovo drive apparirà tra le risorse del computer.




PER MASTERIZZARE UN FILE .ISO

Per prima cosa inserire un disco CD/DVD/BluRay vuoto nel masterizzatore. Quindi clickare con il tasto destro del mouse sul file .ISO e selezionare "Masterizza immagine disco". Il disco verrà masterizzato con i contenuti del file immagine .ISO