Le « problème de l’expression » (Expression Problem) est un défi classique dans la conception des langages de programmation. Dans cet article, nous allons explorer comment l’aborder en Go à l’aide des génériques, en nous référant aux idées présentées dans l’article Featherweight Go et en examinant les limites de l’implémentation actuelle de Go.
Pour comprendre ce défi, il est utile de regarder comment l’article Featherweight Go le définit :
« L’objectif est de définir un type de données par cas, où l’on peut ajouter de nouveaux cas au type de données et de nouvelles fonctions sur ce type de données, sans recompiler le code existant, tout en conservant la sûreté du typage statique. »
L’article fournit une excellente analogie pour aider à visualiser cela :
« La capacité d’un langage à résoudre le problème de l’expression est un indicateur marquant de sa capacité d’expression. On peut considérer les cas comme des lignes et les fonctions comme des colonnes dans un tableau. Dans un langage fonctionnel, les lignes sont fixes (les cas dans une déclaration de type de données), mais il est facile d’ajouter de nouvelles colonnes (des fonctions). Dans un langage orienté objet, les colonnes sont fixes (les méthodes dans une déclaration de classe), mais il est facile d’ajouter de nouvelles lignes (des sous-classes). Nous voulons qu’il soit facile d’ajouter aussi bien des lignes que des colonnes. »
En bref : une véritable solution nous permet d’ajouter sans effort de nouvelles variantes de données (lignes) et de nouvelles opérations/comportements (colonnes) à notre code sans avoir à modifier le code source existant.
La solution proposée dans le brouillon original de Featherweight Go repose sur une fonctionnalité qui n’a pas été retenue dans la version finale des génériques de Go 1.18.
Plus précisément, la solution idéale de l’article suppose que l’on peut appliquer des contraintes de type plus strictes sur le récepteur d’une méthode que celles définies sur la structure de base. Par exemple, idéalement, nous voudrions pouvoir écrire ceci :
// Hypothétique (Non valide en Go) :
// Contraindre directement le récepteur de la méthode
func (e Plus[T Evaler]) Eval() int
func (e Plus[T Stringer]) String() string
Cependant, l’implémentation des génériques en Go exige que les contraintes de type du récepteur de la méthode correspondent parfaitement à la définition de type de la structure. Vous ne pouvez pas ajouter de nouvelles exigences de contrainte (comme Evaler ou Stringer) méthode par méthode.
Comme nous ne pouvons pas contraindre les paramètres de type au niveau des méthodes, nous sommes obligés de les contraindre au niveau de la structure.
Voici un exemple de code adapté qui fonctionne parfaitement avec les génériques de Go 1.18+. Remarquez toutefois que nous sommes contraints de définir au préalable une interface combinée Expr. Parce que nous devons modifier cette interface chaque fois que nous souhaitons ajouter une nouvelle opération, ce n’est pas une véritable solution au problème de l’expression, mais plutôt une solution de contournement pratique adaptée de l’article :
package main
import (
"fmt"
)
// 1. Définir notre type de données de base
type Num struct {
value int
}
// 2. Définir nos opérations (Colonnes)
type Evaler interface {
Eval() int
}
type Stringer interface {
String() string
}
// Implémenter les opérations pour notre type de base
func (e Num) Eval() int {
return e.value
}
func (e Num) String() string {
return fmt.Sprintf("%d", e.value)
}
// 3. Le compromis : nous devons prédéfinir une interface combinée.
// Si nous voulons ajouter une nouvelle opération plus tard, nous sommes forcés de modifier cette interface,
// ce qui enfreint la règle fondamentale du problème de l'expression.
type Expr interface {
Evaler
Stringer
}
// 4. Définir un type de données générique (Ligne)
// Comme Go n'autorise pas les contraintes au niveau des méthodes, nous devons contraindre T à Expr ici.
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. Exécution
func main() {
var e Expr = Plus[Expr]{left: Num{1}, right: Num{2}}
fmt.Println(e.Eval()) // Sortie : 3
fmt.Println(e.String()) // Sortie : (1+2)
}
Bien que les génériques de Go soient incroyablement puissants et apportent une flexibilité indispensable au langage, ils ne résolvent pas nativement le problème de l’expression de la manière initialement envisagée par l’article Featherweight Go. En exigeant que les contraintes soient définies au niveau de la structure plutôt qu’au niveau de la méthode, les développeurs doivent toujours planifier leurs interfaces à l’avance ou modifier le code existant lorsqu’ils ajoutent de nouvelles opérations à des types génériques.
Obtenez l'édition hors ligne de Le Mode de Vie de Kaue—un guide pratique pour optimiser 🍏 Santé, 💰 Richesse, ❤️ Amour et 🧘 Sens.
Recevez une notification lorsque je publie de nouveaux essais, résumés de livres et guides pratiques pour bien vivre. Sans spam—désabonnez-vous à tout moment.
Vous avez une question, une perspective ou une suggestion ? Partagez vos réflexions ci-dessous. Les commentaires fonctionnent avec GitHub Discussions (sans publicité et sans spam)—cliquez sur Se connecter avec GitHub ci-dessous pour commenter ou réagir (créer un compte GitHub gratuit prend moins d'une minute).
Mis à jour le 11 oct. 2026.
AVERTISSEMENT : Le contenu de ce site est fourni à titre purement informatif et éducatif et ne constitue pas un avis médical, psychologique, financier, d'investissement, fiscal ou juridique. Les idées, la sélection et les opinions présentées ici n'engagent que moi et ne représentent pas celles de mon employeur. Les résumés de livres, de conférences et d'articles sont des commentaires éducatifs indépendants et restent la propriété intellectuelle de leurs auteurs respectifs. Des outils d'IA peuvent être utilisés pour aider à l'édition, à la mise en forme et à la synthèse ; bien que tous les efforts soient déployés pour garantir l'exactitude, des erreurs peuvent survenir. CONFIDENTIALITÉ : Ce site utilise des services tiers pour l'abonnement facultatif à la newsletter (follow.it), les commentaires (giscus / GitHub Discussions) et les médias intégrés (YouTube), qui peuvent traiter les adresses IP et les cookies des visiteurs conformément à leurs politiques de confidentialité respectives.