Opérateurs infixes et nième élément d'une liste

Récemment, une question a été posée dans la communauté F#. Un développeur souhaitait définir l'opérateur infixe !! pour accéder au nième élément d'une liste, comme cela se fait en Haskell.

Récemment, une question a été posée dans la communauté F#. Un développeur souhaitait définir l'opérateur infixe !! pour accéder au nième élément d'une liste, comme cela se fait en Haskell. Il a essayé le code suivant, qui échoue :

[fsharp]
let (!!) (L: 'a list) (n : int) = List.nth L n;; 
//val ( !! ) : 'a list -> int -> 'a
 
[1; 2; 3; 4; 5] (!!) 2;;  
// This doesn't work.  I'm not sure why.  Here's the error:
// error FS0003: This value is not a function and cannot be applied

Ce résultat peut surprendre beaucoup de monde. Voyons quelle est la logique et ce qu'il faut faire.

Tout d'abord, ce n'est pas un bogue. Le compilateur détermine si la priorité des opérateurs et s'ils sont préfixes ou infixes en se basant sur le nom de l'opérateur. Après avoir ignoré les éventuels "." et "$", le compilateur regarde le premier caractère du nom. Par exemple, Si c'est "*", ce sera plus prioritaire que "+". Le manuel donne la table exacte de priorités. La majorité de cette table est intuitive : pensez aux opérateurs de base.

En l'occurrence, "!!" commence par le caractère "!". Pas la peine de consulter la table : on sait que l'opérateur "!" sert à accéder à la valeur d'une référence, c'est un opérateur préfixe. Il n'est actuellement pas possible de modifier les priorité et associativité des opérateurs. Pour notre problème, il faut donc utiliser un autre nom.

Cependant, je déconseille cette solution, car il existe une approche plus F#ienne. Pour accéder au nième élément d'un tableau ou d'une chaine de caractères, on écrit foo.[n]. Ne voudrait-on pas utiliser cette même notation pour les listes, par souci de consistance ? Ne voudrait-on pas surcharger l'opérateur .[] ? Pour ce faire, il suffit d'ajouter un accesseur à la propriété Item, à l'objet List. C'est facile, c'est une méthode d'extension :

[fsharp]
type List<'a> with
    member x.Item with get n = List.nth x n

Cela fonctionne à merveille :

[fsharp]
> [1;2;3].[1];;
val it : int = 2

On peut cependant se demander pourquoi cela n'est-il pas fait dans la bibliothèque standard. La raison est simple : la notation .[] donne l'impression d'un accès direct et rapide. Pour une liste, accéder à un élément quelconque n'est pas si rapide. Il faut la parcourir, on a donc une complexité linéaire et proportionnelle à n. Abuser de la fonction List.nth peut résulter en une perte de performances très importante, notamment si cela est fait dans une boucle.

La plupart du temps, on ne souhaite pas accéder au nième élément d'une liste. Cela doit rester une opération rare. Puisque c'est rare, ce n'est pas gênant de taper quelques caractères supplémentaires. Si on a réellement besoin d'accéder au nième élément d'une collection, il est très probable qu'utiliser une liste soit une erreur. Réfléchissez-y à deux fois : une tableau (ou array ou ResizeArray) ne serait-il pas préférable ?