F# è un linguaggio general-purpose, high-level, fortemente tipizzato e multi-paradigma che include programmazione funzionale, imperativa e orientata agli oggetti. Il linguaggio è stato originariamente progettato e implementato da Don Syme; all’interno del team F# dicono che la “F” sta per “Fun”.
Se vieni dal mondo C# o Java, preparati a un cambio di mentalità: F# supporta ben tre paradigmi di programmazione: funzionale, imperativo e orientato agli oggetti. A differenza dei linguaggi OOP classici, in F# “tutto è una funzione”, proprio come in un linguaggio object-oriented “tutto è un oggetto”.
F# semplifica la scrittura di codice conciso, affidabile ed efficiente. Imparerai a scrivere programmi più robusti, con meno bug e in meno righe di codice. Alla fine di questo tutorial avrai configurato il tuo ambiente di sviluppo, scritto le tue prime funzioni F#, compreso l’inferenza di tipo e padroneggiato le basi della programmazione funzionale su .NET.
Prerequisiti
Prima di iniziare, devi avere familiarità con i concetti base della programmazione: sapere cosa sono le variabili, le funzioni, i loop e i tipi di dato. Non serve esperienza in programmazione funzionale anzi, questo tutorial è pensato proprio per chi parte da zero con F#.
Se conosci C# o un altro linguaggio .NET, ottimo: molti concetti ti saranno già familiari. Ma attenzione: per riuscire a programmare in F# serve un cambiamento nell’approccio mentale, perché passare da un paradigma a un altro è sempre difficile.
Strumenti necessari
Per seguire il tutorial ti servono:
- .NET SDK: Il .NET SDK è un ambiente di sviluppo software usato per sviluppare applicazioni .NET. Scaricalo dal sito ufficiale Microsoft (versione 8.0 o superiore consigliata).
- Editor o IDE: puoi scegliere Visual Studio Code (gratuito, multipiattaforma) oppure Visual Studio 2019/2022 (anche l’edizione Community gratuita va benissimo).
- Ionide (per VS Code): Puoi scrivere F# in Visual Studio Code con il plugin Ionide per ottenere un’esperienza IDE cross-platform leggera con IntelliSense e refactoring del codice.
Passo-passo
Step 1 — Installare .NET SDK
Il primo passo è installare il .NET SDK sul tuo sistema operativo. Vai su https://dotnet.microsoft.com/download e scarica l’ultima versione stabile dell’SDK.
Dopo l’installazione, apri il terminale (o PowerShell su Windows) e verifica che tutto funzioni:
dotnet --version
Dovresti vedere il numero di versione del SDK installato (es. 8.0.100). Se il comando non viene riconosciuto, riavvia il terminale o verifica che il PATH sia configurato correttamente.
Suggerimento: Su Windows, potrebbe essere necessario riavviare il computer per aggiornare le variabili d’ambiente.
Step 2 — Scegliere e configurare l’editor
Opzione A: Visual Studio Code
Visual Studio Code è un editor di codice sorgente gratuito, open source e cross-platform che supporta molti linguaggi. F# è supportato dal progetto Ionide.
- Scarica e installa VS Code
- L’unico plugin richiesto per il supporto F# in Visual Studio Code è Ionide-fsharp. Apri VS Code, vai alla sezione Estensioni (Ctrl+Shift+X) e cerca “Ionide-fsharp”. Installa l’estensione.
- Opzionalmente, installa anche “Ionide-FAKE” e “Ionide-Paket” per funzionalità di build e gestione dipendenze.
Opzione B: Visual Studio
Se preferisci un IDE completo:
- Se scarichi Visual Studio per la prima volta, verrà prima installato il Programma di installazione di Visual Studio. Installa l’edizione appropriata di Visual Studio dal programma di installazione.
- Nella pagina Carichi di lavoro seleziona il carico di lavoro “ASP.NET e sviluppo Web”, che include il supporto di F# e .NET Core per i progetti ASP.NET Core.
- Clicca “Modifica” e attendi l’installazione.
Step 3 — Creare il primo progetto F#
Usiamo il .NET CLI per creare un progetto console:
dotnet new console -lang "F#" -o HelloFSharp
cd HelloFSharp
Questo comando crea una nuova directory HelloFSharp con un progetto console F# preconfigurato. All’interno trovi:
Program.fs— il file sorgente principaleHelloFSharp.fsproj— il file di progetto
Apri la cartella in VS Code (code .) o Visual Studio.
Step 4 — Esplorare il codice generato
Apri Program.fs. Dovresti vedere qualcosa del genere:
open System
[<EntryPoint>]
let main argv =
printfn "Hello from F#!"
0 // return an integer exit code
Anatomia del codice:
open System— equivalente ausingin C#, importa lo spazio dei nomi System[<EntryPoint>]— attributo che indica il punto di ingresso del programmalet main argv =— definisce la funzionemainche riceve gli argomenti da riga di comandoprintfn— stampa una stringa seguita da newline0— valore di ritorno (exit code)
Nota: F# è un linguaggio poco verboso, molto espressivo e conciso. Non ama i fronzoli, preferisce l’essenza. Non ci sono parentesi graffe né punti e virgola!
Step 5 — Eseguire il programma
Dal terminale, nella directory del progetto, esegui:
dotnet run
Dovresti vedere l’output:
Hello from F#!
Congratulazioni! Hai appena eseguito il tuo primo programma F#.
Step 6 — Capire l’immutabilità e il binding con let
In F#, quando assegniamo a una “variabile” un valore, questa “variabile” è immutabile nel tempo. Le assegnazioni si fanno usando let.
Modifica Program.fs:
let x = 42
printfn "Il valore di x è: %d" x
let nome = "Alice"
printfn "Ciao, %s!" nome
In F#, x e nome non sono “variabili” nel senso tradizionale, ma identificatori immutabili. Una volta assegnati, non possono essere riassegnati.
Nota: Se vuoi davvero una variabile mutabile, puoi usare mutable:
let mutable contatore = 0
contatore <- contatore + 1
printfn "Contatore: %d" contatore
Ma questa pratica è scoraggiata in stile funzionale!
Step 7 — Definire la tua prima funzione
Definiamo una semplice funzione. Usiamo la keyword let, il nome della funzione, i nomi dei parametri, il simbolo = per l’assegnazione del risultato, e infine l’espressione vera e propria.
let quadrato x = x * x
printfn "Il quadrato di 5 è: %d" (quadrato 5)
Nota come non abbiamo dichiarato il tipo di x né il tipo di ritorno. In realtà F# è un linguaggio statico, ed il tipo viene dedotto implicitamente dal contesto. Il compilatore capisce che x è un int perché viene usato con l’operatore *.
Esegui il programma e vedrai:
Il quadrato di 5 è: 25
Step 8 — Type Inference: lascia lavorare il compilatore
F# è un linguaggio statico, ed il tipo viene dedotto implicitamente dal contesto. Questo si chiama type inference. Prova a hovering col mouse sulla funzione quadrato nel tuo editor: vedrai la signature int -> int, che significa “funzione che prende un int e restituisce un int”.
Se vuoi una funzione che accetti float:
let quadratoFloat (x: float) = x * x
printfn "Quadrato di 3.5: %f" (quadratoFloat 3.5)
Oppure puoi forzare il tipo usando .0:
let quadratoFloat2 x = x * x
let risultato = quadratoFloat2 3.5
Il compilatore deduce automaticamente che x è float perché viene chiamato con 3.5.
Step 9 — Liste e il pipe operator
Le liste sono fondamentali in F#. Ecco come crearle:
let numeri = [1; 2; 3; 4; 5]
let nomi = ["Alice"; "Bob"; "Charlie"]
Il simbolo ; separa gli elementi. Puoi anche creare range:
let da1a10 = [1..10]
Ora vediamo il pipe operator |>, una delle feature più amate di F#:
let sommaQuadrati n =
[1..n]
|> List.map quadrato
|> List.sum
printfn "Somma dei quadrati da 1 a 5: %d" (sommaQuadrati 5)
Il pipe operator passa l’output di un’espressione come input della successiva. Leggi il codice così: “Crea una lista da 1 a n, poi mappala applicando la funzione quadrato, poi somma tutti gli elementi”.
Attenzione: In F#, lo spazio è il separatore standard per i parametri di funzione. Raramente servono parentesi e, in particolare, non usare parentesi quando chiami una funzione.
Step 10 — Pattern Matching: la killer feature
Il pattern matching è una delle caratteristiche più potenti di F#. Ecco un esempio:
let descriviNumero x =
match x with
| 0 -> "Zero"
| 1 -> "Uno"
| 2 -> "Due"
| _ -> "Altro numero"
printfn "%s" (descriviNumero 0)
printfn "%s" (descriviNumero 5)
match è simile a uno switch, ma molto più potente. L’underscore _ è il pattern “catch-all” che matcha qualsiasi valore.
Pattern matching con tuple:
let descriviCoppia (x, y) =
match (x, y) with
| (0, 0) -> "Origine"
| (0, _) -> "Asse Y"
| (_, 0) -> "Asse X"
| _ -> "Punto generico"
Step 11 — Tuple e Record
Tuple:
Se ha una virgola, è una tupla. E una tupla è un oggetto, non due.
let coppia = (42, "risposta")
let (numero, testo) = coppia
printfn "Numero: %d, Testo: %s" numero testo
Record:
I record sono tipi strutturati immutabili:
type Persona = {
Nome: string
Eta: int
}
let alice = { Nome = "Alice"; Eta = 30 }
printfn "Nome: %s, Età: %d" alice.Nome alice.Eta
Step 12 — F# Interactive (FSI): la REPL
Visual Studio fornisce un secondo metodo più comodo per eseguire al volo il codice: un prompt interattivo attivabile dal menu. In VS Code con Ionide, puoi selezionare del codice e premere Alt+Enter per inviarlo a FSI.
Oppure, dal terminale:
dotnet fsi
Ora puoi scrivere codice F# interattivamente:
> let saluta nome = printfn "Ciao, %s!" nome;;
val saluta : nome:string -> unit
> saluta "Mario";;
Ciao, Mario!
Le righe in FSI vengono terminate con ;;. Questo perché FSI consente di immettere più righe. Il ;; alla fine comunica a FSI quando il codice è terminato.
Step 13 — Ricorsione: il cuore della programmazione funzionale
In F#, la ricorsione è preferita ai loop tradizionali. Ecco il classico fattoriale:
let rec fattoriale n =
if n <= 1 then
1
else
n * fattoriale (n - 1)
printfn "Fattoriale di 5: %d" (fattoriale 5)
La keyword rec indica che la funzione è ricorsiva. Assicurati che le funzioni ricorsive siano tail-recursive per prevenire stack overflow.
Versione tail-recursive:
let fattorialeTailRec n =
let rec loop acc n =
if n <= 1 then acc
else loop (acc * n) (n - 1)
loop 1 n
Step 14 — Confronto rapido con C#
C# si basa su principi orientati agli oggetti e imperativi, mentre F# si basa su principi funzionali.
Confronto per somma dei quadrati:
F#:
let sommaQuadrati n = [1..n] |> List.map (fun x -> x*x) |> List.sum
C# (tradizionale):
public static int SommaQuadrati(int n) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i * i;
}
return sum;
}
C# (LINQ, stile funzionale):
public static int SommaQuadrati(int n) {
return Enumerable.Range(1, n)
.Select(i => i * i)
.Sum();
}
F# è più espressivo e ha una sintassi più compatta rispetto alla maggior parte dei linguaggi di programmazione, quindi puoi fare di più con meno codice.
Step 15 — Creare un programma più complesso
Mettiamo insieme tutto. Creiamo un convertitore Pig Latin:
let isVowel (c: char) =
match c with
| 'a' | 'e' | 'i' | 'o' | 'u'
| 'A' | 'E' | 'I' | 'O' | 'U' -> true
| _ -> false
let toPigLatin (word: string) =
if word.Length = 0 then
word
elif isVowel word.[0] then
word + "yay"
else
word.[1..] + string(word.[0]) + "ay"
let frase = "hello world from fsharp"
let parolePigLatin =
frase.Split(' ')
|> Array.map toPigLatin
|> String.concat " "
printfn "Originale: %s" frase
printfn "Pig Latin: %s" parolePigLatin
Questo programma:
- Definisce una funzione
isVowelcon pattern matching - Implementa
toPigLatinche trasforma una parola - Usa il pipe operator per processare un’intera frase
Verifica del risultato
Esegui dotnet run nella directory del tuo progetto. Dovresti vedere output simili a quelli mostrati negli esempi.
Per verificare che il tuo ambiente funzioni correttamente:
dotnet --version # Verifica SDK installato
dotnet fsi # Avvia F# Interactive
In FSI, prova:
> let test = 42;;
> printfn "Test: %d" test;;
Se vedi l’output Test: 42, tutto funziona!
Troubleshooting comuni
In F# probabilmente non arriverai lontano, perché il compilatore è molto più rigoroso. Il miglior strumento per debuggare gli errori del compilatore è il tuo cervello, e F# ti costringe a usarlo!
Errore: “This value is not a function”
Otterrai errori sul passaggio del tipo sbagliato di parametro, o troppo pochi parametri. Esempio:
// Sbagliato:
let somma x y = x + y
let risultato = somma (1, 2) // Errore! Stai passando una tupla
// Corretto:
let risultato = somma 1 2 // Passa due argomenti separati
Errore di tipo mismatch
F# è fortemente tipizzato. Se provi a mixare int e float:
let x = 5
let y = 3.0
let z = x + y // Errore! int + float non è permesso
// Corretto:
let z = float x + y // Converti x a float
Problemi con l’indentazione
I blocchi di codice sono identificati dai rientri di uno o più spazi (indentazione). Se il compilatore si lamenta in modo criptico, controlla l’indentazione:
// Sbagliato:
let funzione x =
let y = x + 1
y * 2
// Corretto:
let funzione x =
let y = x + 1
y * 2
Ionide non funziona in VS Code
Se hai fatto modifiche al sistema o installato prerequisiti Ionide con Visual Studio Code aperto, riavvia Visual Studio Code. Assicurati anche che il .NET SDK sia nel PATH.
Best practice e next step
Best practice:
- Preferisci l’immutabilità: evita
mutablea meno che non sia strettamente necessario - Sfrutta il type inference: dichiara i tipi solo quando serve disambiguare
- Usa il pipe operator: rende il codice più leggibile e “narrativo”
- Pattern matching over if-else: è più espressivo e type-safe
- Metti le cose con “tipi noti” prima delle cose con “tipi sconosciuti”. In particolare, potresti riordinare le pipe e funzioni concatenate in modo che gli oggetti tipizzati vengano prima.
Prossimi passi:
- Studia i Discriminated Unions, una feature killer di F# per modellare domini complessi
- Esplora i Computation Expressions (simili alle monadi)
- Impara i Type Providers per accedere a dati esterni (JSON, database, web) con tipi generati automaticamente
- Leggi il classico “Domain Modeling Made Functional” di Scott Wlaschin
- Sperimenta con Fable per compilare F# in JavaScript
F# gira su .NET — il runtime enterprise più diffuso al mondo. Questo significa accesso a tutte le librerie .NET, compatibilità con C#, e la possibilità di usare F# in produzione con infrastruttura già esistente.
Conclusione
Hai appena completato il tuo primo viaggio in F#! Hai imparato a installare e configurare l’ambiente, scrivere funzioni pure, sfruttare l’immutabilità e il type inference, usare il pattern matching e il pipe operator, e hai capito le differenze fondamentali tra programmazione funzionale e imperativa.
F# ti dà semplicità come Python con correttezza, robustezza e performance oltre C# o Java. Il linguaggio ti guiderà verso codice più robusto e manutenibile, sfruttando la potenza del compilatore per catturare errori prima che diventino bug.
Ora hai le basi per esplorare feature più avanzate. Continua a esercitarti, sperimenta con F# Interactive, e non aver paura di commettere errori: il compilatore F# è il tuo migliore alleato!
Buon coding funzionale! 🚀