L’“Expression Problem” è una sfida classica nella progettazione dei linguaggi di programmazione. In questo articolo esploreremo come affrontarlo in Go utilizzando i generics, facendo riferimento alle idee presentate nell’articolo Featherweight Go ed esaminando i limiti dell’attuale implementazione di Go.
Per comprendere la sfida, è utile guardare a come la definisce l’articolo Featherweight Go:
“L’obiettivo è definire un tipo di dati per casi, in cui sia possibile aggiungere nuovi casi al tipo di dati e nuove funzioni sul tipo di dati, senza ricompilare il codice esistente e mantenendo la sicurezza statica dei tipi.”
L’articolo fornisce un’eccellente analogia per aiutare a visualizzare il concetto:
“La capacità di un linguaggio di risolvere l’Expression Problem è un indicatore saliente della sua capacità espressiva. Si può pensare ai casi come alle righe e alle funzioni come alle colonne di una tabella. In un linguaggio funzionale, le righe sono fisse (i casi nella dichiarazione di un tipo di dati), ma è facile aggiungere nuove colonne (funzioni). In un linguaggio orientato agli oggetti, le colonne sono fisse (i metodi nella dichiarazione di una classe), ma è facile aggiungere nuove righe (sottoclassi). Vogliamo rendere facile aggiungere sia righe che colonne.”
In breve: una vera soluzione ci permette di aggiungere senza sforzo nuove varianti di dati (righe) e nuove operazioni/comportamenti (colonne) al nostro codice senza dover modificare il codice sorgente esistente.
La soluzione proposta nella bozza originale di Featherweight Go si basa su una funzionalità che non è entrata nella versione finale dei generics di Go 1.18.
Nello specifico, la soluzione ideale dell’articolo presuppone che sia possibile applicare vincoli di tipo più restrittivi sul receiver di un metodo rispetto a quanto definito sulla struct di base. Ad esempio, idealmente vorremmo poter scrivere questo:
// Ipotetico (Non valido in Go):
// Vincolare direttamente il receiver del metodo
func (e Plus[T Evaler]) Eval() int
func (e Plus[T Stringer]) String() string
Tuttavia, l’implementazione dei generics in Go richiede che i vincoli di tipo del receiver del metodo corrispondano perfettamente alla definizione del tipo della struct. Non è possibile aggiungere nuovi requisiti di vincolo (come Evaler o Stringer) metodo per metodo.
Poiché non possiamo vincolare i parametri di tipo a livello di metodo, siamo costretti a vincolarli a livello di struct.
Di seguito è riportato un esempio di codice adattato che funziona perfettamente con i generics di Go 1.18+. Si noti, tuttavia, che siamo costretti a definire in anticipo un’interfaccia combinata Expr. Poiché dobbiamo modificare questa interfaccia ogni volta che vogliamo aggiungere una nuova operazione, questa non è una vera soluzione all’Expression Problem, ma piuttosto un compromesso pratico adattato dall’articolo:
package main
import (
"fmt"
)
// 1. Definiamo il nostro tipo di dati di base
type Num struct {
value int
}
// 2. Definiamo le nostre operazioni (Colonne)
type Evaler interface {
Eval() int
}
type Stringer interface {
String() string
}
// Implementiamo le operazioni per il nostro tipo di base
func (e Num) Eval() int {
return e.value
}
func (e Num) String() string {
return fmt.Sprintf("%d", e.value)
}
// 3. Il compromesso: dobbiamo predefinire un'interfaccia combinata.
// Se in seguito vorremo aggiungere una nuova operazione, saremo costretti a modificare
// questa interfaccia, violando la regola fondamentale dell'Expression Problem.
type Expr interface {
Evaler
Stringer
}
// 4. Definiamo un tipo di dati generico (Riga)
// Poiché Go non consente vincoli a livello di metodo, dobbiamo vincolare T a Expr qui.
type Plus[T Expr] struct {
left T
right T
}
func (e Plus[T]) Eval() int {
return e.left.Eval() + e.right.Eval()
}
func (e Plus[T]) String() string {
return fmt.Sprintf("(%s+%s)", e.left.String(), e.right.String())
}
// 5. Esecuzione
func main() {
var e Expr = Plus[Expr]{left: Num{1}, right: Num{2}}
fmt.Println(e.Eval()) // Output: 3
fmt.Println(e.String()) // Output: (1+2)
}
Sebbene i generics di Go siano incredibilmente potenti e portino una flessibilità tanto necessaria al linguaggio, non risolvono nativamente l’Expression Problem nel modo originariamente immaginato dall’articolo Featherweight Go. Richiedendo che i vincoli siano definiti a livello di struct anziché a livello di metodo, gli sviluppatori devono comunque pianificare in anticipo le interfacce o modificare il codice esistente quando aggiungono nuove operazioni ai tipi generici.
Scarica l'edizione offline di Lo Stile di Vita di Kaue—una guida pratica per ottimizzare 🍏 Salute, 💰 Ricchezza, ❤️ Amore e 🧘 Significato.
Ricevi una notifica quando pubblico nuovi saggi, riassunti di libri e guide pratiche per vivere bene. Niente spam—annulla l'iscrizione in qualsiasi momento.
Hai una domanda, una prospettiva o un suggerimento? Condividi i tuoi pensieri qui sotto. I commenti utilizzano GitHub Discussions (senza pubblicità e senza spam)—clicca su Accedi con GitHub qui sotto per commentare o reagire (creare un account GitHub gratuito richiede meno di un minuto).
Aggiornato il 11 ott 2026.
AVVERTENZA LEGALE: Il contenuto di questo sito ha scopo esclusivamente informativo ed educativo e non costituisce consulenza medica, psicologica, finanziaria, di investimento, fiscale o legale. Le idee, la selezione e le opinioni qui presentate sono esclusivamente mie e non rappresentano quelle del mio datore di lavoro. I riassunti di libri, conferenze e articoli sono commenti educativi indipendenti e rimangono proprietà intellettuale dei rispettivi autori. Strumenti di IA possono essere utilizzati per assistere nella revisione, formattazione e sintesi; sebbene venga compiuto ogni sforzo per garantire l'accuratezza, possono verificarsi errori. PRIVACY: Questo sito utilizza servizi di terze parti per l'iscrizione facoltativa alla newsletter (follow.it), i commenti (giscus / GitHub Discussions) e i contenuti multimediali incorporati (YouTube), che possono trattare gli indirizzi IP e i cookie dei visitatori in conformità con le rispettive informative sulla privacy.