URL: https://linuxfr.org/users/linkdd/journaux/golang-oops-you-did-it-again Title: Golang, oops you did it again Authors: David Delassus Date: 2022年03月15日T21:00:47+01:00 License: CC By-SA Tags: golang, generics et déception Score: 23 C'est donc aujourd'hui que sort officiellement [Go 1.18](https://github.com/golang/go/releases/tag/go1.18), avec le support tant attendu des **Generics**. Naïf que je suis, je me dis :> Cool! On va enfin pouvoir implémenter des types `Option` et `Result` pour avoir enfin une gestion d'erreur potable. Allez, je me lance. On commence par le type `Option` (ou *Maybe Monad* pour les intimes). Tout d'abord, on se créé 2 structures, `None` et `Some[T]` : ```go type None struct {} type Some[T any] { value T } ``` On crée ensuite l'interface qui encapsule ces 2 types : ```go type Option[T any] interface { None | Some[T] } ``` Et on crée les constructeurs : ```go func Nothing() None { return None {} } func Just[T any](val T) Some[T] { return Some[T] { value: val } } ``` Puis, on créé une petite fonction `HasValue` qui retourne vrai ou faux selon le type de la monade : ```go func (opt None) HasValue() bool { return false } func (opt Some[T]) HasValue() bool { return true } ``` Jusque là, tout va bien. Maintenant, on va créer la fonction `Map` qui transforme un `Option[T]` en `Option[U]` grâce à une fonction qui transforme `T` en `U`. Le principe est ultra simple : - Si la monade est de type `None` on retourne `None` - Si la monade est de type `Some[T]` on applique la fonction `f : T -> U` sur la valeur contenue, et on retourne `Some[U]` Voici l'implémentation : ```go func (opt None) Map[U any](f func(T) U) Option[U] { return Nothing() } func (opt Some[T]) Map[U any](f func(T) U) Option[U] { return Just(f(opt.value)) } ``` Plutôt simple non ? C'est dommage, ça marchera pas :> method must have no type parameter Et c'est un choix volontaire --> https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#No-parameterized-methods Alors, oui, je pourrais faire : ```go func MapOption[T any, U any](opt Option[T], f func(T) U) Option[U] { // ... } ``` Mais non, `Option[T]` ne peut être utilisé que comme *type constraint*. Ok, alors : ```go func MapOption[T any, U any, OT Option[T], OU Option[U])(opt OT, f func(T) U) OU { switch opt.(type) { // ... } } ``` Toujours pas, on ne peut pas faire de switch sur le type sous-jacent d'un générique. Ok, donc : ```go func MapOption[T any, U any](opt interface{}, f func(T) U) interface{} { switch opt.(type) { case Some[T]: val := opt.(Some[T]).value return Just[U](f(val)) default: return Nothing() } } ``` Oui, ça marche. Mais on perd tout l'intérêt des generics. Et il va falloir s'amuser à cast les `interface{}` dans le bon type à chaque appel. Donc pour l'instant, AMHA, les generics ne servent pas à grand chose si ce n'est composer des interfaces.