title

Posted on Sep 27, 2024

Hopr\Result - ma librairie de Result

Ces 6 derniers mois j’ai assez intensivement utilisé le ResultType de GrahamCampbell sur divers projets, dont un projet Laravel. Je fais beaucoup de PHP et de Laravel pour le travail. C’est une technologie qui est efficace pour du web mais qui manque de beaucoup de choses quand on a regardé un peu ailleurs.

Et comme tout dev, j’ai forcément envie de transposer ce qui est bien ailleurs, dans le langage que je dois utiliser. Pour me simplifier la vie, pour faire ce que je préfère dans le langage que je suis censé utiliser. L’usage de de la monade Result en fait parti.

Pourquoi utiliser ResultType

  1. Elle est disponible avec Laravel, sans dépendance exterieure.
  2. Elle est intégrée à l’écosystème Laravel, un peu.
  3. Elle est maintenue par un contributeur actif et reconnu.

C’est beaucoup en trois points. Vraiment beaucoup.

## Qu’est-ce que je n’aime pas

Toute l’API de cette librairie.

Voyons un exemple :

$user = new User(name: 'Cédric', age: 30);
// Je crée la monade, comme étant un succès, ici ppur l'exemple.
// Dans la réalité, cela serait le produit d'une fonction qui peut aussi retourner une Error.
$m = Success::create($user);
// On va pouvoir chainer
$bar = $m
  ->flatMap(fn(User $user): Result =>
      $user->age >= 18
      ? Success::create('Vas-y')
      : Error::create('Trop jeune.')
  )
  // Seulement si on est un Success, map executera cette fonction.
  ->map(fn(User $user) => 'Take this delicious tea, ' . $user->name)

Qu’est-ce que je n’aime pas ?

  1. La méthode flatMap, je ne comprends pas ce que c’est du premier coup, j’aime pas taper ces caractères, etc. Ce n’est pas clair pour moi. C’est l’argument le plus relatif de la liste.
  2. Recevoir implicitement une option quand je souhaite vérifier si c’est un Success/Error. Je souhaite garder un Result !
  3. Utiliser isDefined()/isEmpty(), au lieu de quelque chose de plus explicite.
  4. Chainer beaucoup d’instructions devient difficile avec seumement les méthodes map et flatMap.
  5. Le Success::create() et le Error::create(), pour moi ce sont des Ok et Error. Et puis ces constructeurs posent problème avec PHPStan, j’ai toujours un message d’erreur sur le type manquant de l’un ou de l’autre.

Ce que je souhaiterais

Moi je souhaiterais tout ça:

  1. Une façon de changer la valeur interne de la monade, par une valeur retournée.
  2. Une façon de changer la monade par une autre (transformer un Success en Error et inversement).
  3. Executer une fonction sans changer le contenu de la monade, simplement.
  4. Récupérer des variables au passage, pour construire un objet futur.

ResultType coche les deux premiers, avec un nommenclature que je n’aime pas.

Du coup voilà ce que je propose, sur le même code:

$user = new User(name: 'Cédric', age: 30);
// Je crée la monade, comme étant un succès, ici ppur l'exemple.
// Dans la réalité, cela serait le produit d'une fonction qui peut aussi retourner une Error.
$m = ok($user);
// On va pouvoir chainer
$bar = $m
  ->bind(fn(User $user): Result =>
      $user->age >= 18
      ? ok('Vas-y')
      : err('Trop jeune.')
  )
  ->map(fn(User $user) => 'Take this delicious tea, ' . $user->name)

Décevant n’est-ce pas ? Quel est l’interet d’une autre nommemclature pour si peu ?

  1. Je préfère bind, qui me semble plus clair : je transforme la monade en ce que la fonction donnée va retourner.
  2. Je ne retourne pas implictement d’option, jamais. Cela devrait être la responsabilité d’une librairie tierce, d’Option, qui crée une option depuis un Result.
  3. Les constructeurs ne font pas crier PHPStan