# Gagnez en perfs avec la méthode lazy en Ruby


La méthode `lazy` a été ajoutée à Ruby il y a maintenant bien longtemps. Ses capacités sont souvent démontrées sur des listes infinies et les données arrivant en continue sur des flux réseaux.

Pendant bien longtemps je n'en avais pas particulièrement trouvé l'utilité. Je n'avais pas ce genre de problématiques et, si je les rencontrais, les traitais avec un autre langage que Ruby.

Cependant, je pense que cette petite méthode peut se révéler bien utile dans un certain nombre de cas. Ces derniers n'ont pas besoin d'être complexes pour que `lazy` apporte un gain de performance, simplement en éliminant du travail inutile.

## Un cas d'usage

Sur un side-project, nous avons un outil qui peut être configuré pour proposer plusieurs stratégies d'authentification. Ces dernières se succèdent jusqu'à l'obtention d'un résultat.

Dans une application non dynamique, le code aurait cette forme :

```ruby
user =
  AuthenticationStrategy1.call(request) ||
  AuthenticationStrategy2.call(request) ||
  AuthenticationStrategy3.call(request)
```

Ruby étant économe, si `AuthenticationStrategy1` retourne un utilisateur, les deux autres stratégies ne seront pas appelées. Cela nous va très bien.

Mais… dès que la liste est définie dynamiquement, les choses commencent à se gâter. Pour illustrer cela, utilisons un tableau de fonctions lambda:

```ruby
auth_strategies = [
  -> { AuthenticationStrategy1.call(request) },
  -> { AuthenticationStrategy2.call(request) },
  -> { AuthenticationStrategy3.call(request) }
]
```

Comment devrions nous nous y prendre maintenant ? L'approche naïve est d'appeler `map` et de trouver ensuite le premier résultat grâce à `find` :

```ruby
auth_strategies = [
  -> { AuthenticationStrategy1.call(request) },
  -> { AuthenticationStrategy2.call(request) },
  -> { AuthenticationStrategy3.call(request) }
]

auth_strategies
  .map { |strategy| strategy.call }
  .find { |result| result.present? }
```

```plain
Appel stratégie 1
Appel stratégie 2
Appel stratégie 3
Utilisateur trouvé par stratégie 2
```

Cela fonctionne mais est relativement peu efficace sans compter que ce pourrait même se révéler dangereux si une stratégie a des effets secondaires (si un connexion était décomptée d'un quota par exemple).

## Notre amie lazy

C'est là que notre amie `lazy` se montre utile. En l'ajoutant dans le mix, la chaine entière est modifiée et seules les stratégies nécessaires vont tourner.

```ruby
auth_strategies = [
  -> { AuthenticationStrategy1.call(request) },
  -> { AuthenticationStrategy2.call(request) },
  -> { AuthenticationStrategy3.call(request) }
]

auth_strategies
  .lazy
  .map { |strategy| strategy.call }
  .find { |result| result.present? }
```

```plain
Appel stratégie 1
Appel stratégie 2
Utilisateur trouvé par stratégie 2
```

## Conclusion

Bon nombre de petits cas comme celui-ci peuvent être traités efficacement et élégament grâce aux lazy enumerators. C'est toujours une bonne technique à se garder sous le coude.

Dernière note : J'ai utilisé ici `present?` pour être explicite mais si vous travaillez en dehors de Rails et cherchez simplement la première valeur « <span lang="en">truthy</span> », vous pouvez vous contenter de passer la méthode `itself` comme block de la méthode `find`:

```ruby
auth_strategies.lazy.map(&:call).find(&:itself)
```

La première valeur « <span lang="en">truthy</span> » sera retournée avec le moins d'opérations possibles.
