Dipendenze esplicite

By abell on 2008-12-15-12:06:30 | In haskell programmazione funzionale

Uno dei punti di forza dello stile di programmazione funzionale è che la dipendenza della valutazione di una funzione dai suoi parametri è espressa in maniera esplicita. Questo può costituire un peso per lo sviluppatore nella fase di scrittura del codice, ma rappresenta un vantaggio notevole nelle fasi successive di debugging e manutenzione.

Nello stile di programmazione imperativa, può (legittimamente) capitare che un frammento di codice

a = 1;
print ( sambucaBottles() );
a = 2;
print ( sambucaBottles() );
dia per risultato la stampa a schermo di
1 bottiglia di sambuca
2 bottiglie di sambuca
Ad esempio, questo accadrebbe se a fosse una variabile disponibile alla funzione sambucaBottles e se questa avesse un'implementazione del tipo (in pseudo-code)
function sambucaBottles () {
  if ( a == 1 ) {
    return str(a) + " bottiglia di sambuca";
  } else {
    return str(a) + " bottiglie di sambuca";
  }
}
Può anche accadere che la funzione sambucaBottles provveda da sola ad incrementare il contatore a, come nella seguente implementazione:
function sambucaBottles () {
  if ( a == 1 ) {
    return str(a) + " bottiglia di sambuca";
  } else {
    return str(a) + " bottiglie di sambuca";
  }
  a = a + 1;
}
In questo caso, il frammento di codice
print ( sambucaBottles() );
print ( sambucaBottles() );
print ( sambucaBottles() );
stamperebbe a schermo
1 bottiglia di sambuca
2 bottiglie di sambuca
3 bottiglie di sambuca
Entrambi i comportamenti sono ragionevoli e, a parte gli esempi un po' forzati, possono essere giustificati all'interno di un programma. Nel primo caso la funzione restituisce un'immagine del mondo al momento della chiamata (dove il mondo in questo caso è il valore di a), mentre nel secondo la funzione si assicura che ad ogni chiamata si abbia un valore diverso del contatore, perché la logica applicativa lo richiede.

In un linguaggio funzionale puro, come Haskell, le definizioni precedenti non sarebbero realizzabili come funzioni. La dipendenza da a andrebbe resa esplicita in questo modo: nel primo caso

sambucaBottles :: Integer -> String
sambucaBottles a =
    show a
    ++ ( case a of 1 -> " bottiglia"; _ -> "bottiglie" )
    ++ " di sambuca"
Il secondo caso non sarebbe realizzabile, in quanto in Haskell non ci sono variabili. Si può però ricevere il contatore come argomento e restituirlo incrementato di 1, perché venga usato nelle invocazioni successive. Una possibile implementazione sarebbe:
sambucaBottles :: Integer -> ( String, Integer )
sambucaBottles a = ( stringres, a + 1 )
  where stringres =
    show a
    ++ ( case a of 1 -> " bottiglia"; _ -> "bottiglie" )
    ++ " di sambuca"

Una differenza fondamentale, a parte l'assenza di effetti collaterali come la modifica di una variabile globale, è che tutte le dipendenze vengono espresse esplicitamente. Questo può rendere l'implementazione un po' più laboriosa (il passaggio ripetuto della variabile a), ma rende più facile ragionare sul codice nel momento della manutenzione e del debugging. L'implementazione di sambucaBottles può non esserci nota, perché viene fornita all'interno di una libreria precompilata, perché il codice sorgente, che pure abbiamo a disposizione, è incomprensibile o perché non abbiamo tempo di studiarlo, ma siamo sicuri del fatto che

Queste garanzie sono preziose. Nei linguaggi imperativi, alcune pratiche di sviluppo possono sopperire, ma il fatto che il linguaggio stesso le imponga in maniera sistematica è una garanzia contro distrazioni o errori.