Introduzione a F# : Parte 1 - Le tre personalità di F#

Prima parte di un articolo introduttivo in tre parti sul linguaggio funzionale F# di Microsoft.

il
Sviluppatore / Ingegnere informatico / Funzionario, Collaboratore di IProgrammatori

Questo è un articolo introduttivo sul linguaggio di programmazione F#, un linguaggio che abbiamo trovato per la prima volta in Visual Studio 2005 e che sembrerebbe avere ancora poca presa tra gli sviluppatori italiani. Ma è davvero così? Lo scopriremo con l’interesse che dimostrerete attraverso i vostri commenti, dopo aver letto quello che stiamo per raccontarvi.

Le tre personalità di F#

In passato sono stati creati molti linguaggi funzionali “puri” che però non hanno trovato grande riscontro sul mercato, attirato di più da linguaggi imperativi o orientati agli oggetti. Infatti, per certi compiti, questi ultimi hanno qualche vantaggio rispetto alla programmazione funzionale.

Questo è il motivo per cui è nato il linguaggio F#, nei laboratori di Microsoft Research, creato da Don Syme che tuttora ne cura gli sviluppi. Il vantaggio di F# è di essere perfettamente integrato con .NET e quindi di supportare ben tre paradigmi di programmazione: funzionale, imperativo e orientato agli oggetti. Questo fa si che F# possa essere utilizzato anche come unico linguaggio di programmazione per sviluppare applicazioni, oppure in sinergia con parti di applicazione sviluppati con Visual Basic o con C#.

Logo di F#
Fig. 1 – Logo ufficiale di F#

Funzionale prima di tutto

Se un linguaggio orientato agli oggetti vede qualsiasi cosa come un oggetto, F# vede qualsiasi cosa come una funzione matematica. Una funzione riceve zero o più parametri (che possono essere valori o funzioni) e restituisce un valore o un’altra funzione. Le funzioni possono essere chiamate da altre funzioni che possono essere chiamate da altre funzioni e così via, fino a raggiungere l’esito voluto.

Le tecniche di F# hanno in parte “contaminato” anche gli altri linguaggi .NET, dato che LINQ, le funzioni lambda e persino il Garbage Collector (la raccolta e distruzione controllata degli oggetti scartati) sono nati sulla base di princìpi funzionali.

Alcune caratteristiche specifiche di F# sono le seguenti:

  • è un linguaggio robusto, privo di effetti collaterali (side-effects) perché non prevede l’uso di Null e lavora con valori immutabili invece che con variabili: non viene mai modificato lo stato attuale, ma si passa invece da uno stato all’altro creando nuovi valori;
  • è un linguaggio poco verboso, molto espressivo e molto conciso: F# non ama i fronzoli, preferisce l’essenza. Niente apertura e chiusura di blocchi di codice, niente parentesi graffe, niente grandi elenchi di parole riservate: si fa tutto con poco e meglio. I blocchi di codice sono identificati dai rientri di uno o più spazi (indentazione).

Queste caratteristiche hanno trovato un campo di applicazione ideale negli ambiti dove è necessaria la massima precisione possibile e dove si elaborano grandi quantità di dati: per esempio in ambito scientifico e accademico, ma anche nelle banche e assicurazioni. Ciò non toglie che F# possa spaziare anche in altri settori, perché nella sua cassetta degli attrezzi ha altri due paradigmi di programmazione.

Come primo esempio vediamo come si scrive un semplice programma per il calcolo di una media tra due numeri interi. La prima versione del programma è quella classica: sommiamo i due numeri e dividiamo per due.

// Media1 = somma di "a" e "b" diviso 2
let media1 a b =
(a+b)/2

printfn "%i" (media1 20 30)

La definizione è composta dalle seguenti parti:

let: è la parola chiave che indica una definizione di espressione;
media1: è il nome della funzione;
a e b: sono i nomi dei parametri passati alla funzione;
=: è il simbolo di assegnazione del risultato dell’espressione;
(a+b)/2: è l’espressione vera e propria.

L’istruzione dell’ultima riga ha il seguente significato:

printfn: visualizza nella Console quello che è specificato di seguito e poi sposta il cursore su una nuova riga;
“%i”: indica il formato in cui dovrà essere espresso il risultato (in questo caso un intero);
(media1 20 30): è la chiamata alla funzione media1, con passaggio dei parametri 20 e 30 rispettivamente agli identificatori a e b.

C’è un’altra versione che possiamo scrivere e che forse è inutilmente più lunga, ma ha il vantaggio di mostrarci come possiamo scrivere delle funzioni più lunghe in F#, comprese delle assegnazioni che rimangono nel campo di visibilità interno alla funzione:

// Media2 = calcola metà della differenza di "a" e "b" e somma ad "a"
let media2 a b =
let differenza = b - a
let mezzo = differenza / 2
a + mezzo

printfn "%i" (media2 100 130)

Se scrivete questo codice in una applicazione Console App (con il linguaggio F#, ovviamente) e avviate l’esecuzione, otterrete 25 nel primo esempio e 115 nel secondo esempio.

Cosa succede se cambiamo il primo esempio, chiamando la funzione media1 con i valori 21 e 30? Semplice, dato che visualizziamo il risultato specificando un risultato intero (“%i”), il valore sarà arrotondato a 25.

Il problema si complica un po’ di più se vogliamo utilizzare valori non interi: in questo caso, infatti, dobbiamo dichiarare il tipo di dato dei parametri e anche del valore restituito dalla funzione, come nel seguente esempio che ci restituisce un risultato uguale a 25.500000:

// Media3 = somma di "a" e "b" diviso 2
let media3 (a:float) (b:float) : float =
(a + b) / 2.

printfn "%f" (media1 21 30)

Orientato agli oggetti

Un aspetto che ha limitato molto i precedenti linguaggi funzionali è l’impossibilità a integrarsi con altri linguaggi e piattaforme, limitando molto il campo di azione.

F# nasce funzionale, ma è anche perfettamente integrato con la piattaforma .NET e con gli altri linguaggi (Visual Basic e C#), tanto che è possibile perfino creare applicazioni “miste”, con soluzioni composte da più progetti, ciascuno di questi scritto in qualsiasi linguaggio .NET.

Questo consente di superare i rigidi limiti del paradigma funzionale, approcciando al mondo degli oggetti senza soluzione di continuità. Ovviamente, l’utilizzo di tecniche di programmazione orientata agli oggetti può introdurre degli effetti collaterali che possono rendere meno robusta l’applicazione. Si tratta però di una opportunità, non di un difetto: è lo sviluppatore che decide consapevolmente fino dove spingersi in una o l’altra direzione, a seconda delle necessità che incontra nel suo percorso di sviluppo dell’applicazione.

Imperativo!

Non poteva mancare il paradigma di programmazione imperativa nella cassetta degli attrezzi di F#, perché possono esserci delle situazioni in cui è più semplice adottare questo approccio oppure è l’unico approccio possibile. Anche in questo caso lo sviluppatore abbandona in parte il solido terreno della programmazione funzionale sottoponendosi a qualche rischio, ma anche in questo caso si tratta di rischio calcolato sulla base di scelte, buone o cattive che siano.

Conclusione

In questa prima parte abbiamo visto i tre paradigmi di programmazione che sono disponibili in F#, con i primi esempi di programmazione funzionale. Nella seconda parte andremo a vedere le modalità con cui possiamo provare le istruzioni di F# e per creare applicazioni vere e proprie.