[Caml] Surcharge et problèmes de cohérence
OCaml, ses problèmes de généricité et son absence de surcharge.
Une question fréquente chez les débutants en Caml concerne le problème d'affichage des valeurs. Quand on utilise Caml en mode interactif, Caml affiche la valeur du résultat, quel que soit son type. Cet outil d'affichage est très pratique et plutôt joli. Il est donc naturel de vouloir l'utiliser aussi dans ses projets (dans l'exécutable final). Cette question revient régulièrement et la réponse habituelle est : « Non, ce n'est pas possible. »
Quand on réfléchit un peu au problème, on se dit qu'il faudrait qu'OCaml garde toutes les informations de types à l'exécution (ce qui permettrait par la même occasion d'avoir de l'introspection). Mais ce serait compliqué à gérer, les performances seraient réduites... Une autre solution serait de gérer la surcharge de fonctions. En effet, il suffirait au moment même du typage de faire les liaisons nécessaires. Par exemple, print serait parfois compilé en print_string, parfois en print_int, etc. selon le type de la valeur. Selon certains, la surcharge pose de nombreux problèmes, affaiblit le typage et réduit l'efficacité de la détection d'erreurs. Ce qui est vrai lorsqu'on en abuse. Mais nous, on veut juste avoir une fonction print. Il est aussi souvent dit que la surcharge n'apporte rien de plus, et qu'ajouter à la main le type après print n'est pas long à faire. Ce qui, selon moi, est faux.
Une remarque que l'on peut faire est : les opérateurs de comparaison (égalité, infériorité, etc.) ont un type 'a -> 'a -> bool. Ils acceptent donc n'importe quel type en entrée. Pourtant, la comparaison entre deux entiers et la comparaison entre deux chaines de caractères ne peuvent pas avoir le même code, au final. C'est donc une forme de surcharge : elle est gérée par le compilateur, une sorte de hack en fait. C'est donc incohérent : pourquoi seuls les opérateurs de comparaison sont-ils surchargés ?
Par souci de consistance (et puisque la vraie surcharge nous a toujours été refusée), il serait bon que les opérateurs de comparaison ne soient plus surchargés. Qu'est-ce que cela changerait au final ? Il faudrait créer plein d'opérateurs de comparaison : par exemple '=' pour les entiers, '=.' pour les flottants, '=^' pour les chaines de caractères. Il en faudrait aussi pour les chars, les booléens, les références... Et puis aussi pour les couples d'entiers, les couples de booléens, les couples avec entier et char, et les couples... et puis aussi pour les triplets... etc. Cela ne suffirait même pas : il faudrait que l'utilisateur définisse lui-même la fonction d'égalité à chaque fois qu'il déclare une structure ou un type somme. Et comme si cela ne suffisait pas, il faudrait également faire tout ce travail pour l'infériorité, la supériorité... Bref, ajouter des dizaines d'opérateurs, ce qui ne serait pas gérable. Surtout que les fonctions comme min, max, List.mem et les types comme Map ou Hashtbl ne pourraient plus exister en l'état.
Ce problème qui survient avec les opérateurs de comparaison aurait dû mettre la puce à l'oreille : plutôt qu'un hack dans le compilateur, une gestion propre de la surcharge aurait été grandement appréciée. D'autant plus que ce hack n'est pas totalement transparent. La comparaison entre deux fonctions n'est pas définie. Mais, puisqu'un opérateur de comparaison a pour type 'a -> 'a -> bool, c'est accepté à la compilation. Pire : le code suivant génère une exception à l'exécution : "let f x = x in f = f". Le problème aurait dû leur mettre la puce à l'oreille parce que leur hack n'en a réglé qu'une partie. À chaque fois que l'on crée un type, on doit souvent définir soi-même sa fonction d'affichage (de même que l'on aurait dû définir les opérateurs de comparaison).
De vraies solutions existent pourtant. En Haskell, ce sont les classes de types. Par exemple, la classe Num regroupe tous les types numériques ; la classe Ord regroupe les types pouvant être comparés et ainsi de suite. Il est d'ailleurs possible d'ajouter des types dans une classe et même de redéfinir la façon dont un type peut être comparé. En Caml, il n'est pas possible de redéfinir l'opérateur de comparaison sur un type particulier. En F#, on a un principe similaire par le biais d'interfaces et par la surcharge. Cela permet notamment d'avoir une fonction print générique que l'on peut redéfinir pour chaque type. Une extension de Caml, GCaml, permet même d'ajouter la surcharge au langage. Cette extension a été faite il y a plusieurs années, mais elle n'a jamais été intégrée à la distribution officielle.
Parmi les autres conséquences, on trouve l'impossibilité de travailler indifféremment sur les listes et les tableaux. On se retrouve donc à dupliquer le code, ou à obliger l'utilisateur à faire des conversions non souhaitées. On se retrouve aussi à dupliquer le code, si on veut avoir une version sur les entiers et une sur les flottants.
Pour finir, admirez le code suivant :
let mean_ilist x = float_of_int (List.fold_left (+) 0 x) /. float_of_int (List.length x
let mean_flist x = (List.fold_left (+.) 0. x) /. float_of_int (List.length x)
let mean_i64list x = Int64.to_float (List.fold_left Int64.add Int64.zero x) /. float_of_int (List.length x)
let mean_iarray x = float_of_int (Array.fold_left (+) 0 x) /. float_of_int (Array.length x)
let mean_farray x = (Array.fold_left (+.) 0. x) /. float_of_int (Array.length x)
let mean_i64array x = Int64.to_float (Array.fold_left Int64.add Int64.zero x) /. float_of_int (Array.length x)
- LLB
- 01:07
- > Lien permanent
- > Commentaires
- > Abus ?


![[Caml] Surcharge et problèmes de cohérence](images_/carre4.gif)