Suite à une discussion sur le procédural et l'objet (http://www.developpez.net/forums/d66...rientee-objet/), une sous discussion (connue aussi sous le nom de "troll") est apparue au sujet des systèmes de types, et de leurs utilité.

Pour certains, le typage est une contrainte inutile à partir du moment où l'on maitrise le développement.
Citation Envoyé par souviron34 Voir le message
Qu'est-ce que "la sûreté de typage" si tu construis correctement ton soft ????
Pourquoi en aurais-tu besoin ???
Citation Envoyé par souviron34 Voir le message
Je crois que vous sous-estimez largement ET la capacité d'analyse des informaticiens en général ET les méthodes de développements trraditionnelles...

Comment bien régler son compilateur

Je répète que, avec un compilateur bien réglé (ce qui se fait dans tout développement sérieux), les problèmes cités n'apparaissent jamais... A la limite à la première compil, si jamais tu as eu la tête en l'air, mais jamais plus tard.
Je lance donc un nouveau fil de discussion, pour qu'on puisse élargir le "débat" à autre chose que l'opposition "objet" vs "procédural", et ce d'autant plus que les langages avec les "meilleurs" système de types sont (généralement) des langages fonctionnels, qui ne tombent donc pas strictement dans une de ces deux catégories.

=============================

Je vais commencer par un exemple de pourquoi, à mes yeux, une absence de système de type digne de ce nom est problématique.
Prenons le langage C. Supposons que je veuille écrire une fonction qui trie un tableau contenant des chaines de caractères. Je vais pour cela utiliser la fonction qsort de la librairie standard. Sa "signature" (j'emploierai ce mot pour le type d'une fonction, à savoir le type de ses arguments et celui de sa valeur de retour) est :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
void qsort( void *buf, size_t num, size_t size, int (*compare)(const void *, const void *) );
Le premier void signifie que la fonction ne retourne rien. Elle travaille donc en place.
Ensuite, on a un void *buf. Ceci signifie 'un pointeur d'un type indéfini". C'est en fait l'adresse du tableau à trier, mais le système de type de C n'est pas capable de l'exprimer. Les deux argument suivant sont la taille du tableau (en nombre de case) et la taille des éléments dans le tableau (probablement en nombre d'octet, mais surtout résultat de sizeof). Enfin, le dernier argument est un pointeur vers une fonction de comparaison. Le système de type nous apprends uniquement que la fonction doit retourner un entier, et attendre deux arguments qui sont des pointeurs vers des types quelconque, et qu'elle ne peux pas les modifier. Ce sont en fait des pointeurs vers les éléments du tableau, mais là encore, le système de type de C n'est pas dutout assez puissant pour préciser ça.

Essayons de l'utiliser :

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


static int compare (void const *a, void const *b)
{
   char const *const pa = a;
   char const *const pb = b;

   return strcmp (pa, pb);
}

/* affichage du tableau */
static void aff (char const **a, size_t n)
{
   size_t i;
   for (i = 0; i < n; i++)
   {
      printf ("%s\n", a[i]);
   }
}


int main (int argc, char *argv[]){

  char const *tab[] = { "world", "hello", "wild" };


  qsort(tab, 3, sizeof (char* ), compare);

  aff (tab, 3);

  return 0;

}
La fonction compare compare des chaines de caractère, la fonction aff affiche un tableau de chaines.
En suivant les conseils de Souviron34, je compile avec la commande
Code : Sélectionner tout - Visualiser dans une fenêtre à part
gcc -ansi -O2 -Wchar-subscripts -Wcomment -Wformat=2 -Wimplicit-int -Werror-implicit-function-declaration -Wmain -Wparentheses -Wsequence-point -Wreturn-type -Wswitch -Wtrigraphs -Wunused -Wuninitialized -Wunknown-pragmas -Wfloat-equal -Wundef -Wshadow -Wpointer-arith -Wbad-function-cast -Wwrite-strings -Wconversion -Wsign-compare -Waggregate-return -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wmissing-noreturn -Wformat -Wmissing-format-attribute -Wno-deprecated-declarations -Wpacked -Wredundant-decls -Wnested-externs -Winline -Wlong-long -Wunreachable-code tri.c
Rien que ça... Et tout se passe sans la moindre encombre.

Maintenant je lance le programme, et résultat...
Je ne sais pas vous, mais moi ça ne me semble pas bien trié. Pour info, si on analyse l'exécution avec Valgrind, il ne détecte pas le moindre problème.

Le problème vient de la fonction compare qui considère que son argument est l'adresse d'un tableau d'octet, alors que c'est l'adresse de l'adresse d'un tableau d'octet. Donc la fonction de comparaison compare n'importe quoi, et le résultat est absurde. Et contrairement à ce que prétends souviron34, le compilateur n'y peut rien. C'est à dire que l'oublie de 4 petites étoiles de rien du tout rend le programme totalement faux.



Qu'est ce qu'un système de type peut nous apporter ?

Tout d'abord quelques notations pour la suite (pour info, c'est du OCaml, mais en Haskell, ce serait pareil, et en Java, 15 fois plus verbeux, mais plutôt proche ):
Quand j'écris un type comme :
c'est le type d'une fonction qui attends deux arguments, le premier de type int, le second de type char, et retourne un booléen.
Un type comme
signifie que l'on attends un entier et que l'on retourne un paire d'entier.

Pour compliquer un peu, on va introduire ce que l'on appelle des "variables de type". Supposons que j'ai une fonction qui attends un argument x, et retourne la paire (x, x). Une telle fonction fonctionne quelque soit le type de l'argument. On va donc écrire comme type :
Ce type dit "pour tout type 'a, ma fonction attend un 'a et retourne une paire de 'a". Si on veut l'appliquer à un entier, elle prendre le type
et si on veut l'appliquer à une chaine de caractère, ce sera le type
Code : Sélectionner tout - Visualiser dans une fenêtre à part
strint -> (string * string)
C'est ce que l'on appelle des fonctions "polymorphe" (pour les puristes, c'est du polymorphisme paramétrique. On verra plus bas ou dans un autre post parce qu'il est tard qu'il existe aussi tu polymorphisme d'inclusion)

Ainsi, la fonction qui attend une paire et retourne son premier argument aura le type
Première chose à noter sur le type d'une fonction : il peut nous en apprendre long sur la fonction elle même. Quand on a une fonction du type ci dessus, qui dit "quelque soit les types 'a et 'b, si tu me donnes une paire constituée d'un 'a et d'un 'b, je te retourne un 'a", on sait en fait que (sauf si le programmeur de la fonction est un névrosé !) c'est la fonction qui retourne la première composante de la paire.

Pour se rapprocher de ce qu'on a écris plus haut, regardons le type des tableaux. Contrairement à C, avec un vrai système de type, on peut distinguer un tableau d'un simple pointeur. Un tableau d'entier sera donc un int array. Un tableau de flottant, un float array, etc.

Maintenant, quelle peut être le type d'une fonction triant les tableaux ? En prenant exemple sur le C, on voit qu'il faut bien sûr le tableau, mais aussi une fonction de comparaison des éléments. En revanche, dans tout langage de raisonnablement haut niveau (et c'est le cas de tout langage bien typé), un tableau transporte la taille de ses éléments ainsi que leur nombre. Ces deux arguments sont donc inutiles. On va donc pouvoir donner le type suivant à notre fonction de tri :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
sort : ('a -> 'a -> int) -> 'a array -> unit
Ce type nous dit que quelque soit le type des éléments de notre tableau, la fonction sort attends une fonction de type 'a -> 'a -> int et un tableau de 'a et retourne unit.
unit est en gros l'équivalent de void en C, et signifie donc que la fonction ne "retourne rien" et travaillera donc en place. La fonction attendue en argument est la fonction de comparaison (elle retourne un entier négatif si son premier argument est plus petit, un entier positif s'il est plus grand, et 0 si les deux arguments sont égaux).

Quelle est la grosse différence par rapport au type de la fonction qsort en C ? Les deux sont polymorphes et attendent n'importe quel type de tableau, et une fonction de comparaison. Mais en C, la fonction de comparaison attend n'importe quel type d'argument. Alors qu'ici, le système de type impose que les deux arguments attendus par la fonction de comparaison soient effectivement du même type que les éléments du tableau. L'erreur de tout à l'heure est en fait impossible.

Le système de type n'est pas ici une contrainte, mais un garde fou qui nous protège de nos erreurs. On peut trier autant de tableau qu'en C, tout aussi facilement. Mais on ne peut pas se tromper en utilisant une fonction de comparaison qui n'est pas adaptée aux éléments du tableau que l'on est en train de trier.

Après ce "post introductif", je continuerai (sans doute demain) à expliquer pourquoi, à mes yeux, un système de type doit être vu comme une force du langage et une aide précieuse à la programmation, et non comme une contrainte.

Juste pour info, en OCaml, le code C ci dessus se traduit en :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
let main () =
  let a = [|"world"; "hello"; "wild" |] in
  Array.sort String.compare a;
  Array.iter (Printf.printf "%s\n") a;;

main ()
Et le résultat est bien