[Atom] [Mail] [Twitter]
Liens : git · hacks · divers · cabale · buzz! · à propos +
Au menu
\/

Pourquoi le C est moins puissant que votre langage favori

#. Par rz0 dans Le code et ses raisons. Publié le 27.03 2010 à 02:38. 3 commentaires.
<. Le code et ses raisons : typedef en C
c débutants langages

On entend souvent dire que l’on peut tout faire dans un langage comme dans un autre, Turing-complétude, tout ça. La variante de ce discours est que l’on peut tout faire en C parce que c’est un langage de bas niveau. Certes, on peut tout faire, mais on ne peut pas tout faire aussi bien, c’est-à-dire aussi efficacement.

Ainsi, avant de poursuivre avec mes articles sur les techniques de programmation en C, j’ai décidé de prendre le temps d’écrire un court billet sur ce que l’on ne peut pas faire en C. Ce petit texte n’a pas pour prétention d’être exhaustif, car la liste de ce que l’on ne peut pas faire en C est sûrement longue, très longue. Mais je souhaite donner ici quelques points de réflexion et principes de base, pour les plus débutants d’entre nous.

Le problème

Pourquoi donc ne peut-on pas faire aussi efficacement en C certaines choses que l’on peut bien faire dans un autre langage ? La réponse tient en cela : l’abstraction.

L’argument que l’on entend souvent est le suivant : le C étant plus bas niveau, il suffit de recoder les mécanismes internes abstraits par tel ou tel langage de plus haut niveau. Je suis d’accord avec cette méthode, je l’aime même beaucoup, étant moi-même assez spécialisé dans l’implémentation des langages. Mais il est important d’en connaître les limites.

Les limites, ce sont les limites de définition du langage. Si l’on veut utiliser le C comme un langage raisonnablement portable, et que l’on s’en tient à la norme, on hérite par la même occasion de contraintes normées, plus fortes que celles imposées par la plateforme pour laquelle on développe.

Concrètement, cela signifie que sur une machine donnée, votre beau C portable ne pourra pas recourir aux mêmes astuces que l’implémentation d’un langage de plus haut niveau (qui, elle, n’est pas portable). Prenez par exemple les variables de Scheme, ou tout autre langage dynamique. Dans ces langages, une variable peut pointer sur un objet, ou contenir une valeur numérique unboxed. En profitant de la représentation des pointeurs au sein du système hôte, et du fait que la mémoire n’y est jamais allouée que sur un alignement de 2, on peut utiliser un bit pour déterminer la nature de l’objet (boxed ou unboxed).α C’est malin comme tout, et vieux comme le monde. Mais inapplicable en C. Question de portabilité. En effet, on n’a même pas la garantie que les pointeurs soient convertibles en entiers !

Mais, mais, me direz-vous, en Scheme non plus, on n’a pas cette garantie ! Oui… mais en Scheme, il n’y a pas de pointeurs comme en C ! Autrement dit, le programmeur s’en contrefiche : il utilise simplement son langage, et c’est au compilateur de décider comment telle ou telle fonctionnalité est traduite au niveau de la machine ; du point de vue du langage, on a perdu…

α : Ce n’est pas un article sur l’implémentation des langages, voyez la page Wikipédia sur l''unboxing', si vous n’êtes pas à l’aise avec ces notions.

La solution ?

Mais on n’a qu’à implémenter des solutions spécifiques en plus de la version générale moins efficace ! C’est en effet une possibilité.

Par exemple, pour les objets dynamiquement polymorphes pointeurs / entiers, on pourrait se définir un petit jeu de macros dans ce genre-là :

#include <stdint.h>

#if UINTPTR_MAX && HAVE_2ALIGNPTRS

typedef uintptr_t VariantInt;
typedef uintptr_t Variant;

#define ISINT(x) ((x) & 0x1)
#define INTVAL(x) ((x) >> 1)
#define PTRVAL(x) ((void *)(x))
#define SETINT(x, y) ((x) = (y) << 1 | 0x1)
#define SETPTR(x, y) ((x) = (uintptr_t)(y))

#else

#if UINTPTR_MAX
typedef uintptr_t VariantInt;
#else
typedef long VariantInt;
#endif
typedef struct {
        union {
                VariantInt _int;
                void *_ptr;
        } _val;
        unsigned _isint: 1;
} Variant;

#define ISINT(x) ((x)._isint)
#define INTVAL(x) ((x)._val._int)
#define PTRVAL(x) ((x)._val._ptr)
#define SETINT(x, y) ((x)._val._int = (y), (x)._isint = 1)
#define SETPTR(x, y) ((x)._val._ptr = (y), (x)._isint = 0)

#endif

Bref, un truc du genre. Pas le plus beau jeu de macros du monde, mais vous comprenez le principe.

Et vous vous attendez sans doute maintenant à ce que je réfute cet argument… et bien non ! En réalité, c’est une manière parfaitement viable d’étendre un peu le langage. Cependant, elle requiert quelques précautions.

  • Elle demande d’être méthodique, en cela qu’il faut patiemment écrire une macro (ou une fonction) pour abstraire chaque opération affectée par le changement d’implémentation. Cela peut être long, fastidieux, et si l’on teste plus une implémentation qu’une autre, on court le risque de se laisser biaiser, et d’oublier d’isoler des fonctionnalités qui devraient l’être.

  • Elle tend à faire basculer les meilleurs programmeurs du côté obscur de la non-portabilité. :-° En effet, certains hacks marchent tellement biens que l’on est vite tenté de se dire qu’il ne sert à rien de maintenir une version générique, sous-optimale, mais conforme à la norme. Et c’est là un gros risque ! Parfois, on peut effectivement se laisser aller…

    C’est une question subjective, et je ne peux certainement pas décider à votre place. Je ne peux que vous offrir une règle générale que je m’applique de manière plus ou moins stricte :

    Plus le code se veut général (bibliothèques, composants réutilisables), plus il est important de maintenir une version standard, portable, de l’implémentation.

    Un avantage de cette stratégie est que vous pouvez en toute tranquillité utiliser l’implémentation simple, non optimisée, par défaut, et doucement migrer les différents systèmes, au cas par cas, vers votre code spécialisé, au fur et à mesure de vos tests (ou de ceux de vos utilisateurs !).

    Mais bien sûr, il ne faut pas se focaliser sur la portabilité, qui, de toute manière, est toute relative. En effet, si, par exemple, votre application dépend déjà fortement d’une bibliothèque tierce, telle que la GLib, qui elle-même fait certaines hypothèses sur l’implémentation, il peut être raisonnable de vous appuyer dessus.

Que retenir de tout ça ?

Au final, je dirais, pas grand chose, si ce n’est que j’essaierai, pour ma part, d’être précis quant aux implications des méthodes que je décris. Ce n’est pas (seulement) pour être pédant ; je pense qu’il est réellement important de comprendre (pour mieux ignorer, dirons certains) les limites des définitions et des standards que l’on accepte, parfois sans le dire.

À bientôt donc, pour de nouvelles aventures ! :)

[ tag:blog.huoc.org,2009:posts/47 ]
Voir les commentaires · Commenter

/\ \/

Le code et ses raisons : typedef en C

#. Par rz0 dans Le code et ses raisons. Mis à jour le 27.03 2010 à 02:40. 3 commentaires.
<. Le code et ses raisons : goto en C
c débutants idiomes pratique programmation typedef

Voici un vieil article qui traînait au fond de mes dossiers. Comme il semble presque terminé, je me suis dit que j’allais le poster tout de même…

Aujourd’hui donc, au menu, typedef, le bien nommé, qui, très naturellement, permet de définir un type, c’est-à-dire lui associer un nouveau nom. En pratique, toutefois, les choses ne sont pas si simples et il est bon de rappeler quelques règles d’utilisation fondamentales avant d’attaquer la partie plus « philosophique » de notre billet :

  • typedef s’utilise comme une classe de stockage, dans une déclaration classique ;

  • à ce titre, un nom de type ainsi défini ne peut l’être qu’une seule fois dans une unité de compilation (fichier C plus en-têtes inclus) ;

  • enfin, l’identificateur résultant de cette opération est un synonyme du type utilisé : l’ancien et le nouveau type sont utilisables de manière interchangeable.

typedef, pourquoi

Il y a maintes raisons pour lesquelles quelqu’un peut vouloir utiliser typedef, mais il est possible de les ranger en deux familles :

  • celles qui visent à simplifier l’écriture : p.ex. en renommant un pointeur de fonctions, ou en permettant d’omettre le mot-clé struct ou union ;

  • et celles qui introduisent une abstraction (p.ex. un type entier de taille variable selon l’architecture, ou encore un type opaque).

Les deux usages ont leurs défenseurs et, dans une certaine mesure, leurs détracteurs. Si le second emploi ne semble pas contestable en tant que tel, c’est plutôt la nécessité de l’abstraction qui est souvent remise en cause. Et si le premier apparaît comme une puissante manifestation de flemme, l’histoire a montré que de grands noms le soutiennent.

On peut citer à cet effet Bjarne Stroustrup et son C++, dans lequel la déclaration d’une variable de type structure ou union ne requiert pas de mot-clé particulier.

typedef abusif ?

Il y a une école, relativement répandue, notamment dans le milieu scolaire, dont la position est de systématiquement créer un synonyme pour toute structure ou union définie. Et, à l’inverse, il existe certains cercles où cette pratique est violemment condamnée.α Elle est entre autre explicitement déconseillée par le KNF d'OpenBSD.

La raison invoquée est la suivante : utiliser typedef sur une structure engendre une abstraction non toujours voulue. Lorsque celle-ci est conçue comme un simple agrégat de données, et ses membres pensés pour un accès direct, typedef masque la nature du type réel à l’utilisateur.

Dans l’autre camp, la parole est à l’uniformité. En effet, il existe une multitude de niveaux, ou plutôt de formes, d’abstraction, au-delà du simple qualificatif opaque, et son contraire. À partir de là, en quoi est-il légitime de différencier (et à quel niveau cette distinction doit-elle se faire) la structure directement accessible du type abstrait ? Si un objet a une partie de ses membres accessibles, doit-il utiliser le mot-clé struct ?

α : Pour information, je suis également de ceux qui s’y opposent.

Zoom sur l’idée d’abstraction

Bien plus intéressant que les questions de raccourcis d’écriture, la notion d’abstraction est celle qui domine réellement ce débat. S’abstraire, mais de quoi ?

On peut s’abstraire du matériel, tout d’abord, ou plus généralement de la plateforme, du système : la définition de types (le plus souvent entiers) susceptibles de changer d’une architecture à une autre est une pratique commune, connue depuis longtemps sous Unix et en C, en général. Les diverses normes en contiennent elles-mêmes une collection importante : size_t en ANSI-C, off_t dans POSIX, ou encore les fameux intXX_t du C99.

Au niveau au-dessus, on peut vouloir s’abstraire de l’implémentation. Par exemple, il existe de nombreuses façons d’écrire un dictionnaire, mais l’interface basique est la même : on peut y chercher, insérer et supprimer. Nommer son type Dict est une manière courtoise d’indiquer ses intentions : on n’a besoin que de cette interface-ci.

Typiquement, on se retrouve alors avec un type concret, disons struct htable, qui modélise une table de hachage, que l’on souhaite masquer derrière le type Dict. La bonne pratique du C veut que l’on passe en paramètre de nos fonctions un pointeur vers struct htable. La question qui divise est alors la suivante : faut-il utiliser typedef la structure ou le pointeur vers celle-ci ?

Ça a l’air idiot, n’est-ce pas ? Pourtant, il n’y a pas vraiment de bonne réponse à cette question… et pour s’en convaincre, essayons d’entrer dans le raisonnement des deux partis.

La question fondamentale est : faut-il exposer la structure de pointeur ? Si l’on redéfinit le type structure alors on garde la structure de pointeur dans toutes nos interfaces ; si l’on redéfinit le type pointeur, alors les interfaces ne montreront qu’un objet complètement opaque.

Dans un camp

Un premier point de vue est de dire que l’objet se comporte comme un pointeur (car c’est un pointeur !) donc l’analogie est justifiée. On pourrait dire que cela restreint l’implémentation (on pourrait vouloir utiliser une sorte d’ID quelconque). Mais étant donné que la majorité des implémentations raisonnables, en C, utilisent une forme ou une autre d’objet en mémoire dont on ne manipule qu’une référence (le pointeur), la limitation est relativement virtuelle.β

De plus, on peut aussi décréter que les types abstraits se manipulent comme des types scalaires (entiers, pointeurs, etc.) et donc finalement partagent plus ou moins toutes les propriétés de ceux-ci (égalité, affectation, passage en argument, etc.). De ce point de vue, les types obtenus en redéfinissant le type pointeur vers structure se mêlent mieux aux types de base du C, tandis qu’en redéfinissant le type structure lui-même, les opérations disponibles sur celui-ci sont limitées ; en général, il n’est même pas permis de déclarer une variable de ce type (seulement du type pointeur associé).

β : Le seul domaine que je vois où elle pourrait devenir réellement contraignante est la programmation système : le noyau conserve souvent un certain nombre d’informations sous forme de tables indexées, plutôt que de pointeurs vers des objets directement en mémoire, ces objets n’étant pas forcément situés (entièrement) dans l’espace mémoire de l’utilisateur.

Cependant, il peut arriver qu’il soit plus commode de manipuler les indices au lieu des pointeurs eux-mêmes, notamment si l’identificateur entier est réutilisé pour indexer d’autres structures.

Et dans l’autre

Cependant, si l’on opte pour le typedef simulant un type scalaire, quelque part, on dissimule une information capitale : chaque objet de ce type en cache un autre, « plus gros ». En d’autres termes, ce ne sont que des références.

À l’inverse, en conservant explicitement la syntaxe des pointeurs, cette notion est clairement identifiée et passer un pointeur à une fonction souligne le potentiel de celle-ci à modifier une donnée tierce, masquée par le pointeur.

À cela, les défenseurs de l’autre camp pourront rétorquer qu’une structure de données complexe cache en général elle-même de multiples niveaux d’indirections, et qu’ainsi il est tout aussi trompeur de faire croire à l’utilisateur que c’est l’objet directement pointé qui sera sujet à modification : bref, que l’on ne présente ainsi que le haut de l’iceberg.

Bien sûr, il est facile de considérer que la syntaxe n’est qu’un indice conventionnel signalant au programmeur que de la mémoire indirectement manipulée entre en jeu. Tout comme il est facile d’y opposer le fait qu’à l’usage cela est invisible (le typage étant réservé au prototype) et que la documentation a déjà à sa charge ce genre de détails, ou encore que d’autres techniques sont plus appropriées (p.ex. l’usage d’un qualificateur de fonctions pure tel qu’on le trouve dans GCC). On pourrait contre-attaquer en remarquant que le typage devrait alléger la documentation, à quoi on pourrait répondre que celui du C est de toute façon trop faible pour cela… J’aime débattre avec moi-même ; tout cela n’est qu’opinion, au final. :)

Conclusion

Malgré toutes ces années à coder en C, je ne suis pas parvenu à forger un avis prononcé sur la question. J’oscille moi-même entre les diverses opinions, à mesure que de nouveaux argumentaires en faveur d’une pratique ou d’une autre me parviennent…

Ma position actuelle, si elle intéresse quelqu’un, relève d’un point de vue que je qualifierais de « minimaliste » : je me suis fixé, pour mes nouveaux programmes, de n’utiliser typedef que lorsque j’y vois un intérêt immédiat (p.ex. pour la portabilité des types entiers). Le raisonnement derrière cette politique est que l’abstraction est, en C et au niveau où je travaille, relativement pauvre, et qu’un changement d’implémentation ne saurait généralement se passer d’une altération de l’API tout entière. Abstraire seulement le typage ne suffit pas. Abstraire davantage entraîne un risque de complexité accrue et de performances moindres.

Mais je ne suis pas pour autant fondamentalement opposé à l’usage de typedef dans d’autres scénarios. Une bibliothèque de très haut niveau qui reposerait sur la manipulation d’objets (au sens POO) pourrait choisir de nommer ses classes avec typedef. Une telle convention aurait du sens.

Je concluerai donc sur ces mots : il faut ainsi prendre en compte le contexte et établir des conventions. Pour rabâcher le principe de style fondateur et bien connu : après le bon sens, la cohérence doit primer.

[ tag:blog.huoc.org,2009:posts/37 ]
Voir les commentaires · Commenter

/\ \/

Pourquoi attacher tant d'importance au typage ?

#. Par bluestorm. Publié le 30.12 2009 à 13:34. 15 commentaires.
débutants langages méditation typage

La réponse, enfin dévoilée ! Bluestorm Origins: The Typing Mystery, maintenant dans les blogs (well, le nôtre du moins :-°). —rz0

Il est arrivé une bonne chose à mon dernier billet : des gens m’y ont répondu en commentaire. Je les remercie tous chaudement. Ce n’est pas complètement rationnel, mais je trouve ça vraiment appréciable, et ça m’a motivé pour retenter l’expérience de ces billets « en bref ». Mais pas pour cette fois : c’est intéressant quand on a différentes choses à dire, et il vaut mieux que ce ne soit pas trop souvent, sinon ils deviennent de plus en plus vides et répétitifs.

Dans les commentaires, que j’ai lus avec intérêt (j’ai même répondu à certains, c’est fou les blogs), se trouve une question qui m’a donné envie d’en faire un billet à part entière. C’est le commentaire de robocop :

Il y a quelque chose qui ressort quand même pas mal dans nombre de tes articles, c’est l’importance que tu attaches aux types. J’ai la facheuse impression d’être passé à coté de quelque chose de fondamental, pour moi, les types ce n’est qu’une vérification supplémentaire qui est fait à la compilation et ou à l’interpretation, mais à la façon dont tu en parles, le système de type semble le cœur même d’un langage de programmation.

Si comme je le pressens, je loupe vraiment quelque chose, pourrais-tu disserter rapidement là-dessus ?

En une phrase : pourquoi, parmi toutes les caractéristiques d’un langage de programmation, attacher autant d’importance à son système de typage ?

J’ai été surpris de ne pas pouvoir répondre à cette question sur le moment. J’ai pris du temps pour y réfléchir, et je pense avoir maintenant une réponse satisfaisante (mais pas rapide).

J’ai apprécié les commentaires de la carotte qui, avant l’ours, a relu ce texte et m’a permis de l’améliorer.

Rappels

Je ne compte pas expliquer dans ce billet les divers avantages et inconvénients, existants ou fantasmés, des différents systèmes de typage ; ce n’est pas le sujet. Je me contenterai ici d’un bref rappel de deux notions qui reviennent souvent quand on discute les systèmes de typage, et dont je reparlerai par la suite :

typage statique

désigne les systèmes de typage qui vérifient les types dans un programme avant de l’exécuter, typiquement au moment de la compilation. Les erreurs de typage, s’il y en a, sont signalées pendant cette phase et le programme n’est pas exécuté tant qu’il n’est pas correctement type. En général, à l’exécution, on n’a plus à se soucier des types et on peut raisonner comme s’ils n’existaient plus.

typage dynamique

désigne les systèmes de typage qui vérifient les types pendant l’exécution du programme. Avant chaque opération que le programme doit exécuter, il vérifie que les types des données sont correctes ; cela implique que le programme a accès à ces types quelque part : ils sont stockés dans la mémoire du programme pendant son exécution.

Ce sont deux méthodes très différentes qui ont un but commun : empêcher l’ordinateur d’effectuer des opérations mal typées (« mélanger des patates et des carottes »). Chaque façon de faire a ses adeptes, et les deux camps se livrent des guerres terribles, depuis plusieurs générations de langages de programmation — et, bientôt, d’informaticiens. Le point important ici est que les langages mettant en place l’une ou l’autre de ces méthodes ont un système de typage. Il sera plus ou moins bien défini, selon la précision de la définition du langage lui-même, mais dans tous les cas on peut s’y intéresser.

J’ai déjà parlé du typage dans un article publié sur le SdZ : Le typage, présentation thématique et historique. Je n’en suis pas complètement satisfait : je le trouve trop jeune et un peu naïf, et j’essaierai peut-être de le reprendre quand je saurai mieux ce que je veux dire.
Pour les débats autour du choix d’un système, j’aime bien l’article What To Know Before Debating Type Systems. Il est peut-être un peu biaisé en faveur des systèmes statiques, et un peu trop technique, mais il apporte une vue d’ensemble assez intéressante. C’est une bonne base si vous voulez vous lancer dans le débat : statique ou dynamique ?

Cependant, ce que je voudrais discuter ici, ce n’est pas le choix de telle ou telle méthode de typage, mais plutôt la place du typage au sein d’un langage de programmation, relativement aux autres aspects du langage.

Types comme information

Imaginez que vous voulez écrire un petit morceau de programme dans le langage de votre choix, qui soit réutilisable par d’autres programmeurs. Vous devez leur décrire ce que vous avez fait. Vous commencez par leur dire ce que fait ce bout de programme (« c’est une compression selon le codage de Huffman »), et vous allez décrire la manière dont ils doivent interagir avec ce bout de programme pour profiter de ses bienfaits : « on lui donne un texte, et il le renvoie compressé, avec le dictionnaire de compression ». Avec cette simple phrase, vous avez déjà transmis des informations de typage : on lui fournit un texte, donc en général une chaîne de caractère. S’il a besoin de plus d’informations (par exemple sur la nature du dictionnaire de compression), vous donnerez une description plus précise qui contiendra autant d’informations de typage supplémentaires.

Cette façon de procéder est commune à l’ensemble des programmes et langages. Choisissez un langage que vous appréciez et qui a une documentation utilisable : elle contient (plus ou moins explicitement) des informations de typage. Par exemple, j’ai choisi une page au hasard de la documentation du langage PLT Scheme, réputé « non typé », dans laquelle on peut trouver le texte (code ?) suivant :

(directory-exists? path) → boolean?
  path : path-string?

C’est tout à fait du typage : on décrit une fonction qui vérifie qu’un répertoire existe dans un système de fichier. Cette fonction accepte un chemin de type path-string, et renvoie un boolean. Les points d’interrogation soulignent un détail intéressant : les noms choisis pour décrire les types sont aussi des fonctions (prédicats) que l’on peut utiliser pour vérifier qu’une valeur est bien du type donné. Par exemple la documentation de la fonction path-string? contient :

(path-string? v) → boolean?
  v : any/c

Ces prédicats sont utilisés au sein de ces fonctions pour vérifier que l’utilisateur ne donne pas n’importe quoi, et la vérification de validité est donc effectuée à l’exécution : c’est une forme de typage dynamique.

Ce ne sont pas les seules explications concernant ces fonctions, il y a aussi du texte en langue naturelle qui explique leur usage. Certaines documentations ne mettent aucune information de typage aussi claire (je pense à Python par exemple), il faut aller la pêcher dans le texte explicatif, ou encore elle peut être implicitement contenue dans les noms des paramètres, qui respectent des conventions facilitant cette tâche.

L’idée importante c’est qu’au fond, un type c’est essentiellement une information sur une expression du langage de programmation.

Le langage qui parle de lui-même

Cette idée des types comme informations est très large, car elle doit faire cohabiter les notions de typage très différentes selon les langages. Elle est peut-être même un peu trop étendue, puisqu’elle désigne tout et n’importe quoi, et que je ne vais pas avoir de mal à vous convaincre que vous avez besoin d’informations pour coder correctement. On peut affiner un peu plus.

Quand on parle du système de typage d’un langage, on n’a pas en tête les conventions de nommage des paramètres utilisées dans sa documentation. On parle en général d’une manière de décrire ces informations qui fait partie du langage lui-même : le système de typage, c’est la partie du langage qui permet de donner des informations sur les valeurs.α Je désigne cette partie par le terme langage de types : par exemple le langage des types du C est un sous-ensemble du langage C qui contient les types de base (int, float..), des modulateurs (unsigned), enum, struct, …, des pointeurs, tableaux, et même des types fonctionnels.

En d’autres termes, le système de typage, c’est un méta-langage, qui détient le pouvoir d’expression du langage sur lui-même. Plus le système de typage est expressif, plus on peut dire de choses sur un programme au sein de ce programme, plutôt que dans la documentation ou en commentaire.

Vous comprenez sans doute pourquoi, avec ce point de vue, j’attache tellement d’importance, quand je m’intéresse à un langage de programmation, à son système de typage : c’est une partie toute fondamentale puisqu’elle détermine la capacité des programmes dans ce langage, en plus de faire quelque chose d’utile, à transmettre des informations sur ce qu’il fait.

Il faut cependant bien remarquer que, bien que très importante, elle n’est pas non plus indispensable : on peut très bien imaginer des langages qui ne permettent de formuler aucune information sur les programmes, mais qui ont d’autres qualités qui en font des langages intéressants. Je pense par exemple à Forth, un des premiers langages à pile haut niveau.

α : On pourrait croire à une circularité ici : si les types parlent du langage tout en en faisant partie, alors les types parlent des types et le monde s’écroule. En réalité, la plupart des langages de types ne sont pas suffisamment expressifs, et ne parlent que d’une partie restreinte du langage (qu’on appelle souvent les valeurs). Mais il existe effectivement des langages (par exemple Coq) dans lesquels les types ont eux aussi un type, qui a à son tourβ un type… Il peut bien sûr exister un nombre infini de types différents, bien qu’un programme donné ne les utilise pas tous en même temps.

β : « Turtles all the way down ! » ;-)

Système de typage et vérification

La définition actuelle reste assez molle : est-ce que les prédicats de PLT Scheme constituent un système de typage ? Ils font partie du langage, mais leur utilisation est laissée au bon vouloir de l’utilisateur : il peut aussi implémenter sa fonction sans faire aucune vérification. On pense en général plutôt à des systèmes dans lesquels le typage est obligatoire, et vérifié automatiquementγ par le compilateur. Ce qui est sûr c’est que les informations implicites de la documentation Python ne rentrent pas dans ce cadre : le typage en Python, c’est ce que le compilateur sait vérifier de lui-même et afficher dans un message d’erreur : l’appartenance à une classe, l’existence d’une méthode donnée, etc.

Quand on décrit le système de typage d’un langage, il faut donc aussi préciser la manière dont ces informations sont vérifiées, ou plus généralement exploitées par le reste du langage. Il y a d’autres distinctions que la vérification statique ou dynamique que j’ai évoquée. Par exemple, certains langages permettent des conversions implicite d’un type vers un autre, ou le choix dans une fonction d’un comportement différent selon le type des paramètres ; on parle dans ce dernier cas de polymorphisme ad-hoc, car il permet de gérer chaque cas particulier séparément.

Autres points à considérer pour son système de typage

L’expressivité et les conditions de vérification ne sont pas les seuls points à débattre d’un système de typage.

On peut s’intéresser à l’emplacement, au sein des programmes de ce langage, des annotations de typage. Certains langages demandent au programmeur très peu d’indications de typage, car ils peuvent les reconstruire à partir des informations déjà présentes dans le langage : c’est l’inférence de type. Plus le langage des types est expressif, plus cette reconstruction est difficile. Les annotations peuvent encombrer le programme si elles sont trop nombreuses, mais placées judicieusement elles peuvent augmenter la lisibilité. Plus généralement, les informations de typage, qu’elles soient produites par le programmeur ou ses outils, renforcent la documentation et la modularité entre les différentes partie du programme : donner le type des valeurs d’une bibliothèque logicielle facilite beaucoup son utilisation par d’autres programmeurs, surtout quand il est assez expressif pour traduire l’ensemble des contraintes d’utilisation de cette interface.

Un dernier exemple, plus sophistiqué, la propriété de séparation : dans certains langages (dont OCaml), le typage est une phase totalement séparée de l’évaluation, dans le sens où une fois qu’on a un programme bien typé, on peut oublier complètement tout ce qui concerne le typage, et cela ne change absolument rien au déroulement du programme.δ Dans d’autres langages, en particulier ceux qui possèdent le polymorphisme ad-hoc, il n’y a pas cette séparation. L’avantage de cette propriété est qu’elle donne un langage plus simple à comprendre, avec des phases plus indépendantes. L’inconvénient est qu’elle est contradictoire avec certaines fonctionnalités que les concepteurs du langage peuvent avoir envie de fournir : imposer la séparation peut réduire l’expressivité. Il n’y a pas de bon et de mauvais choix dans l’absolu, cela dépend des besoins du langage, mais c’est un choix auquel les systèmes de typage sont confrontés. On peut enfin imaginer des distinctions plus fines, avec une partie du langage qui brise la séparation, mais qui est traduit dans une première phase vers un langage plus simple, bien séparé. C’est comme cela par exemple qu’on manipule les type classes de Haskell quand on veut en donner des définitions très précises (formelles).

γ : En fait, PLT Scheme permet aussi la vérification des types ; je suis impressionné par cette implémentation de Scheme.

δ : Je parle ici de la sémantique du langage, c’est à dire d’une définition un peu abstraite du comportement (ou sens) des programmes. Ce n’est pas une question d’implémentation : si on crée un compilateur pour un langage avec cette séparation, on peut très bien choisir de faire des optimisations en raisonnant sur le type des variables, par exemple en changeant leur représentation mémoire (boxing, etc.), du moment que cela crée aucune différence de comportement du programme observable par le programmeur.

Contraintes et limitations

Si le langage des types est peu expressif, et la vérification très rigoureuse (impossible de définir des termes non typés), le typage peut vite devenir une contrainte. Par exemple, le langage des types du C ne permet pas d’exprimer le polymorphisme paramétrique (une fonction qui marche sur tous les types ayant structure donnée) ; on fait avec, en utilisant une sorte de type-à-tout-faire, void *, qui permet d’abandonner à la fois l’information de typage et la restriction qui y est associée.

Cette limitation que l’on rencontre n’est pas seulement un artefact de systèmes pratiques, mais se trouve déjà dans la théorie des langages. Par exemple, le lambda-calcul simplement typé de base ne permet pas d’écrire des programmes qui ne terminent pas : tous les programmes bien typés terminent en un temps fini. Ça a ses avantages, mais aussi un inconvénient : ce langage n’est pas Turing-complet. En gros, il y a des fonctions qu’on ne peut pas écrire avec. On peut lever cette limitation,ε soit en enrichissant le langage des types (polymorphisme plus types récursifs par exemple), soit en ajoutant des constructions au reste du langage (opérateur de point fixe ou définitions de fonctions récursives).

Les langages au typage dynamique ont en général un langage de type assez faible, mais ont du coup l’avantage de ne poser que très peu de contraintes : comme ils ne vérifient pas grand chose au niveau du typage (par exemple, ils ne vérifient pas le fait que les deux branches d’une condition renvoient le même type), ils ne posent pas non plus de contraintes gênantes. C’est un autre choix qui peut se justifier ; par exemple, certains motifs de conception de la programmation objet sont notoirement difficilesζ à typer correctement (ils demandent un langage très expressif), les langages dynamiques n’ont pas ce problème.

ε : Limitation qui n’en est pas toujours une : on peut faire des choses très intéressantes avec des langages non Turing-complets (par exemple les expressions régulières/rationnelles, quand elles le sont vraiment, ou Coq).

ζ : Jacques Guarrige, Code reuse through polymorphic variants

The idea for this example originally comes from a post by Phil Wadler on the Java-Genericity mailing list W+98, in which he proposed a solution to the Expression Problem, that is the problem of extending a variant type with new constructors without recompiling code for old ones. A similar problem and solution can be found in KFF98, but untyped. It appeared later that Wadler’s solution, which already supposed an extension of Generic Java, itself an extension of Java, could not be typed. Didier Rémy, Jérôme Vouillon and myself finally came up with a typable solution in an object-oriented extension of the 3rd order typed lambda-calculus, which was later checked correct by Haruo Hosoya in F-omega- sub-rec. On the other hand, I could provide a much shorter solution in about 15 lines of Objective Label using polymorphic variants, which were merged later in Objective Caml. The solution used only standard ML-polymorphism, which is a weakened 2nd order typed lambda-calculus, and can be inferred automatically.

Pour bien typer, il faut bien comprendre

Il arrive donc assez souvent que des idées sur de nouvelles façon de programmer soient développées dans un cadre non typé, et qu’elles trouvent seulement ensuite leur place dans un système de typage, comme par exemple le transfert du langage de programmation Oz, langage dynamique qui a popularisé certains paradigmes de concurrence, à AliceML, langage typé qui reprend une partie de ces fonctionnalités.

Cela ne veut pas dire que le cadre non typé est plus fructueux : exprimer précisément une information statique sur ces nouvelles idées permet souvent de les éclairer d’un jour nouveau. Par exemple, la recherche de sémantiques statiques pour les langages à effets de bords a donné lieu à l’idée de monade, qui est utilisée en Haskell pour représenter, entre aures, les effets de bords, et qui a pour conséquence de modifier le type des programmes en y rendant ces effets explicites. On y a gagné des outils intéressant, un point de vue nouveau, et une meilleure compréhensionη de la programmation impérative.

Malheureusement, la programmation est un sujet compliqué dont nous n’avons fait qu’égratigner la surface : il y a beaucoup de choses qu’on ne sait pas encore expliquer de manière simple et précise à la fois, par exemple la concurrence, les manipulations mémoire, la communication avec les programmes extérieurs, etc. L’explication et le typage précis de manières avancées de programmer est un sujet de recherche actif, dont on espère qu’il nous apportera des réponses ; en attendant, il faut se contenter de typages correspondant à l’état des connaissances aujourd’hui, et qui ne sont pas forcément aussi expressifs qu’on le voudrait.

η : Rz0 me signale que cette « meilleure compréhension » n’a pas du tout affecté la pratique des programmeurs qui, sur le terrain, utilisent des effets de bords, et qu’il est donc un peu « osé » de présenter les monades comme une réussite de ce point de vue. C’est en effet un peu plus compliqué que ça : notre compréhension théorique des effets de bords s’est améliorée, mais on a aussi découvert que les monades sont des outils trop généraux, qu’elles s’appliquent à beaucoup de choses qui ne sont pas considérées habituellement comme des effets de bords, et qu’elles ne sont pas toujours assez précises.

Pour la gestion de la mémoire par exemple, une monade ne peut pas raisonner, au niveau du typage, sur l’étendue des zones mémoires affectées par un programme : la monade saura dire de deux programmes qu’ils modifient la mémoire, mais pas si les zones de mémoire qu’ils utilisent sont disjointes ou s’il y a des interactions entre les deux. On a besoin de ces propriétés plus fines, et on est en train de développer des outils plus spécialisés et plus complexes, comme les systèmes d’effets et les logiques de séparation.

Enfin, même si ces idées mettent du temps à sortir de leur cadre théorique pour atterrir dans la boîte à outils des programmeurs, elles restent présentes dans l’industrie, avec par exemple les outils d’analyse statique de programmes, qui suivent d’assez près les progrès de la recherche dans ce domaine. L’arrivée du concept dans un langage de programmation grand public est la dernière étape (après l’entrée dans les langages expérimentaux comme DDC ou ATS), qui se fera plus tard, ou peut-être pas du tout, si l’on constate que les bénéfices de ces méthodes ne compensent pas l’ajout en complexité.

η´ : Et puis d’abord, si l’exemple des monades ne vous plaît pas, en voici un autre : les continuations. Le comportement dynamique de call/cc est bien connu, mais l’étude de son lien avec le typage et la logique classique est au moins aussi intéressante.

Puissance du typage et complexité du langage

Cette difficulté que rencontrent les concepteurs de langage de programmation se retrouve aussi, à plus petite échelle, quand un programmeur essaie de donner un type statique à son programme. Quand on code dans un langage dynamique, on ne se soucie essentiellement que d’une chose : il faut qu’au moment de l’exécution, notre programme marche. Donner un type très expressif à un programme, cela peut demander de rendre explicites plus d’informations : qu’est-ce que notre programme fait, comment le fait-il, pourquoi cette fonction est-elle correcte ? L’ordinateur peut en deviner une partie (c’est le principe de l’inférence des types), le programmeur en connaît une partie (par exemple ce qui est un entier, et ce qui est un booléen dans son programme), mais il y a parfois des programmes compliqués pour lesquels ce n’est pas aussi simple : donner un type très précis impose une bonne compréhension du programme.

Bien entendu, même dans les langages typés, un programmeur a toujours la possibilité d’utiliser des représentations moins expressives, et qui demandent donc moins de réflexion. Par exemple, au lieu d’avoir un type de donnée spécialisé pour décrire les dates (jours, mois, heure, etc.), on peut par exemple utiliser des chaînes de caractères. C’est généralement une mauvaise idée : un effort au départ pour décrire et utiliser un type plus précis permet ensuite des manipulations plus sûres et plus directes.

Il y a cependant un point à ne pas oublier quand on cherche des langages de types expressifs : ils rendent nécessairement les langages plus complexes. Quand des débutants en Ada doivent comprendre les différences entre les paramètres in, out, et in out, ils peuvent penser qu’un langage faisant moins de fioritures aurait été plus simple. On peut se demander s’il vaut mieux concevoir des langages simplifiés pour les débutants et des langages moins simples mais ayant d’autres qualités pour les experts, ou si une taille unique peut convenir à tout le monde. Cette question intéressante est encore ouverte, mais il est clair que la simplicité (réelle ou perçue) d’un langage joue dans son adoption par les programmeurs, et qu’on ne peut pas se permettre de concevoir des systèmes de typage arbitrairement compliqués en espérant qu’ils vont dominer le monde.

Tous les langages sont typés, parfois sans le savoir

Ma définition est suffisamment large pour qu’on puisse trouver un système de typage à tous les langages, même ceux qui se proclament traditionnellement « non typés ». Les cas les plus extrêmes sont les langages dont les types sont tellement pauvres qu’ils ne peuvent essentiellement rien dire d’intéressant. Par exemple, en Brainfuck, le système de typage est vide : on ne peut rien dire sur les programmes, et d’ailleurs il n’y a rien à dire puisqu’ils ne manipulent jamais rien d’autre qu’une bande mémoire dont une case est pointée.

Plus utiles, les assembleurs sont aussi des langages avec des typages très pauvres : il y a en général des registres, des valeurs immédiates et des adresses mémoires (et par exemple des labels à plus haut niveau), et des considérations de taille, alignement, etc., qui sont assez riches mais ne nous apprennent pas beaucoup sur le comportement à plus haut niveau du code, tant qu’on ne le munit pas d’un système de typage plus expressif.θ On peut encore citer le langage de Matlab/Octave, qui ne connaît essentiellement que les matrices de scalaires, ce qui peut parfois donner de très mauvaises surprises.

θ : Voir par exemple les travaux de Greg Morrisset ou George Necula sur les assembleurs fortement typés.

La réaction de rz0 de sujet souligne un autre aspect du problème :

À mon avis, un point intéressant, et pas du tout développé dans la plupart des visions « haut niveau » de l’assembleur, c’est les contraintes sur les adresses ou la mémoire : représentation, décalage, facteur, taille, alignement, granularité, etc. Je considère que c’est du typage aussi, et c’est du typage « utile », dans le sens où si les données fournies aux opérandes sont mal typées, soit ça n’assemble pas (p.ex. parce qu’il n’y a pas assez de bits pour représenter l’adresse entièrement), soit ça balance une exception au runtime (si on passe un registre et que sa valeur ne vérifie pas les conditions de typage). Bref, je trouve que ce n’est pas « rien à dire ».

Mélanger typage statique et typage dynamique ?

Cette idée de décrire les langages peu typés comme possédant un petit nombre de « types poubelles » ne donnant que très peu d’information peut en fait être utilisée pour mélanger au sein d’un langage le typage statique et le typage dynamique : on peut rajouter ces types poubelles à l’éventail des types du langage.

Il s’agit de permettre de « perdre » localement l’information de typage, en transformant toutes les valeurs, quelle que soit leur type, vers un même type dynamic. On peut ensuite utiliser une fonction d’extraction pour récupérer des valeurs bien typées dans les parties du programme qui n’ont pas besoin de typage dynamique. Cette idée est vieille comme le monde, il s’agit de faire un cast (conversion d’un type vers un autre) au détriment de la sûreté du typage, et elle est présente dans un grand nombre de langages de programmation.

Pour que cette méthode ne nuise pas trop à la sûreté de l’application dans son ensemble, il faut réutiliser les méthodes de vérification de typage des langages dynamiques : quand on envoie la valeur vers le type poubelle, on lui associe une autre valeur runtimeι qui décrit son type. Ensuite, quand on convertit à nouveau la valeur dynamique vers un type plus expressif du langage, on peut insérer un test pendant l’exécution qui vérifie que le type d’arrivée est bien compatible avec le type dynamique stocké avec la valeur.

Au-delà du dynamic_cast de C++, les langages fortement typés ont fait des tentatives dans cette direction. La solution la plus simple est de demander à l’utilisateur de ces fonctions d’insertion et extraction dans le type dynamique de manipuler lui-même les types dynamiques, en utilisant une bibliothèque logicielle dédiée. On peut utiliser des fonctionnalités du langage pour simplifier cette manipulation (par exemple des macros syntaxiques, des types fantômes,κ ou des type classes), et mettre en place des bibliothèques logicielles apportant ces facilités aux programmeurs.

Ça n’est cependant pas suffisant : si l’on se contente d’une bibliothèque logicielle à part, le langage ne garantit pas la cohérence entre l’information statique de typage dont il dispose, et les valeurs dynamiques construites par l’utilisateur ou sa bibliothèque spécialisée : même quand elle est fortement typée (les manipulations à l’extérieur de la bibliothèque sont sûres) il faut faire confiance à l’auteur de cette bibliothèque de valeurs dynamique pour n’avoir pas fait d’erreurs de conversion à l’intérieur.

ι : Un anglicisme qui désigne le « moment de l’exécution », à nuancer de compile-time ou parse-time, et dont j’ai beaucoup de mal à me débarrasser.

κ : C’est ce que j’ai utilisé dans Macaque ; j’avais besoin d’une méthode sûre pour traiter la perte de typage des données passant par le serveur SQL (qui n’utilise que des chaînes de caractères), et je me retrouvé, sans m’en rendre compte au départ, avec du typage dynamique fait maison.

Si l’on veut plus de simplicité et de sûreté, il faut intégrer cette fonctionnalité directement au sein du langage de programmation. La communauté CAML a fait partie des pionniers de ce domaine avec Dynamics in ML, une extension du langage proposant des valeurs dynamique même pour les types polymorphes. Les langages fonctionnels Clean et AliceML proposent aussi cette fonctionnalité.

On peut remarquer que cette approche (construction automatique, par le langage directement, de la description runtime de type) brise la propriété de séparation, que j’ai évoquée précédemment, entre la phase de typage et la phase d’exécution.

Ces méthodes permet de concevoir des langages typés statiquement mais qui acceptent de « laisser la main » gracieusement quand on atteint les limites de leur expressivité. Cela permet de résoudre les problèmes des constructions trop difficiles à typer mentionnés auparavant, sans abandonner trop de typage. Malheureusement, en pratique la mise en place de tels systèmes au sein d’un langage peut compliquer sensiblement son implémentation efficace, et elles ne sont donc pas très répandues. C’est encore une fois un sujet de recherche actif. Il y a des langages qui essaient de rendre encore plus transparente la frontière entre les mondes dynamiquement et statiquement typés. Cela demande de nouvelles méthodologies de typages, et tout une zoologie se crée autour du sujet : soft typing, gradual typing, blame typing, hybrid typing, …

En attendant, les programmeurs se contentent en général de méthodes plus indirectes, en utilisant des valeurs dans des types moins expressifs mais plus souples, comme le font sans le savoir les utilisateurs de langages dynamiques.

Les types ne font pas tout

Ce billet a beaucoup parlé des types, et de pourquoi le système de typage est un ingrédient majeur d’un langage de programmation. Il ne faut pas non plus oublier que ce n’est pas le seul, et qu’une grande partie de l’intérêt des langages ne vient pas du tout de leur système de types.λ

L’un des livres sur la programmation qui m’a beaucoup plu est le CTM : Concepts, Techniques and Models of Computer Programming. C’est un livre vraiment formidable, qui a étendu mon horizon en matière de techniques de programmation, et qui pourtant ne parle pas du tout, ou quasiment pas, de typage.

λ : Même si vous seriez sans doute surpris par la quantité de chose qu’on peut présenter sous forme d’un typage, ou en relation avec le typage : sécurité, performances, invariants de structures de données, etc.

[ tag:blog.huoc.org,2009:bluestorm/8 ]
Voir les commentaires · Commenter

/\ \/

Premiers pas vers un environnement de test NetBSD/Xen

#. Par rz0. Publié le 20.12 2009 à 15:50. 4 commentaires.
débutants ensimag netbsd xen

Loin des préoccupations über-théoriques de bluestorm, ces temps-ci, moi, j’ai essayé de bosser un peu, pour la Junior Entreprise locale. Autant dire que ça ne se passe pas tellement bien : on manque de main d’œuvre compétente et sérieuse, et honnêtement, ça m’irrite au plus haut point que le client soit perpétuellement déçu de notre travail ; c’est comme si je faisais du mauvais travail, mais franchement, si je savais que je me taperais autant de glands à gérer et autant de boulot pour lequel je n’ai pas signé, je n’aurais jamais accepté ce job. Bref, paraît qu’il y a des années avec et des années sans, bah là c’est clairement une année sans.

Tout ceci me mène à une petite anecdote : au cours de mon travail, je suis amené à tester le produit que je développe (la nature duquel est un secret…) dans une perspective de déploiement sur serveur. Récemment, je me suis heurté à des bugs que je n’arrive pas à reproduire sur ma machine de développement, pour une raison ou pour une autre (je n’ai pas encore réglé le souci), qui semble être liée au déploiement lui-même, du moins la configuration employée. Du coup, pour bien faire, je me suis dit que j’allais monter un petit serveur de test… un petit serveur virtuel !

Et ça tombe bien, j’ai toujours trouvé que Xen, ça avait l’air sympa ! Et en plus, chez NetBSD, on semble accorder une certaine importance au support Xen, ce qui est plutôt cool. Je me suis donc mis en tête de configurer un joli petit environnement NetBSD+Xen, le but étant d’avoir un serveur virtuel tournant sur une NetBSD vanilla, avec le minimum de paquetages nécessaires au déploiement de mon application. Bref, un truc propre.α

α : Le véritable serveur tourne sous Linux, mais pour du test rapide, il est beaucoup plus simple pour moi de virtualiser du NetBSD ; j’écrirai peut-être un autre billet, si je me fais un test Linux+Xen.

Xen pour les innocents

Pour me la jouer à la blueblue, je vais dire deux mots pour ceux qui ne connaîtraient pas le principe. Ahem, donc pour ceux qui ne connaîtraient pas, Xen est une solution de virtualisation,β comme VirtualBox… mais pas tellement en fait.

β : … principalement destinée aux serveurs, mais hey !

Avec VirtualBox, vous avez votre système normal et par-dessus, un petit logiciel qui fait tourner en son sein un autre système d’exploitation.

Xen est un peu différent. Vous ne pouvez pas simplement lancer Xen depuis votre système préféré, comme vous lanceriez un programme quelconque… c’est en fait plutôt l’inverse : Xen est une espèce de méta-système (un hyperviseur) qui va se charger de lancer vos systèmes d’exploitation un à un ! Si donc avant vous aviez, disons une Ubuntu avec une NetBSD dans votre VirtualBox, vous auriez maintenant l’hyperviseur Xen, et deux domaines (c’est comme ça que ça s’appelle dans Xen) : votre Ubuntu, et votre NetBSD.γ

γ : Pour tout vous dire, ce n’est probablement pas le meilleur choix… Ubuntu ne semble pas intégrer, dans sa ligne de développement centrale le support de Xen. Voyez la documentation communautaire d'Ubuntu sur Xen pour plus de détails.

Tous les domaines ne sont pas égaux pour autant : il y a toujours un système principal, appelé dom0 (domaine 0) ; les autres sont les domaines U (domU). Le dom0, c’est un peu le root de tous vos domaines : il a tous les pouvoirs, tandis que les autres ont des droits restreints, notamment concernant l’accès au réseau et au matériel. Au démarrage, Xen lance le dom0, qui a à sa charge de créer les domU qu’il désire (ou plutôt d’ordonner à Xen de les créer). Dans notre exemple, Ubuntu est le dom0, et NetBSD, le seul domU.

La dernière chose à savoir est que pour pouvoir être géré par Xen, il faut que le noyau soit conçu pour dialoguer avec l’hyperviseur, qui fait office d’abstraction entre la machine et le système. Il y a des noyaux dom0 et des noyaux domU. NetBSD propose dans sa distribution officielle des noyaux dom0 et domU ; sous Gentoo, compiler un noyau Xen est juste une histoire de changer de paquetage sources ; je ne sais pas trop ce qu’il en est des autres distributions Linux…

Bref, si vous ne connaissiez pas Xen, voilà qui est réglé. Il est temps de passer aux choses sérieuses…

Notes d’installation

Peut-être parce que c’est destiné à des mecs un peu underground, des sysadmins et tout, il n’y a pas beaucoup de tutos sur Xen… du moins, il y en a, mais beaucoup ne sont plus à jour et offrent des informations périmées. Notamment, le howto officiel de NetBSD/xen est, en l’état, incorrect.δ C’est fort dommage car mettre en place un environnement Xen avec NetBSD est en réalité très simple… pour peu que l’on sache comment s’y prendre.

J’offre ici mes humbles notes d’installation. Sait-on jamais, peut-être mon expérience servira-t-elle à quelqu’un.

δ : Si je mets la main sur ces permissions de commit un jour, je devrais ptet y faire quelque chose… Normalement, je fais signer ma clef GPG ce lundi !

  1. Si NetBSD est livré avec tout ce qu’il faut pour le faire tourner comme domaine Xen, Xen lui-même (l’hyperviseur et les outils pour le contrôler depuis le dom0) est à installer via pkgsrc. Il y a un jeu de paquetages par version majeure de Xen. Dans mon cas, j’ai pris la 3 :

    # cd /usr/pkgsrc/xenkernel3 && make install
    # cd /usr/pkgsrc/xentools3 && make install
    

    Le noyau Xen s’installe très logiquement dans la hiérarchie /usr/pkg. À l’heure où j’écris ces lignes, le fichier en question est /usr/pkg/xen3-kernel/xen.gz.

    On aura aussi besoin de scripts de démarrage pour les démons Xen xend et xenbackendd. J’ai simplement recopié ceux fournis à l’endroit habituel :

    # ln -s /usr/pkg/share/examples/rc.d/xend /etc/rc.d/
    # ln -s /usr/pkg/share/examples/rc.d/xenbackendd /etc/rc.d/
    

    Pour l’instant, ces démons ne font rien ; ils ne peuvent même être lancés (mais on peut déjà les ajouter dans le rc.conf, histoire de ne pas oublier). Il nous manque un dom0…

  2. Concernant le dom0, la bonne nouvelle, c’est que seul le noyau a besoin d’être modifié : juste en changeant de noyau au démarrage, on peut décider d’utiliser Xen ou pas, tout ça avec le même jeu de programmes, les mêmes disques et systèmes de fichiers, etc.

    Pour se faire un noyau dom0, rien de plus simple : un noyau dom0 est fourni avec NetBSD (XEN3_DOM0) ; en partant de sa configuration, j’ai simplement modifié le fichier à ma convenance (comme j’ai fait pour le noyau GENERIC, pour obtenir ma configuration non Xen), puis config et make ont fait le reste, comme d’habitude :

    # cd /usr/src/sys/arch/conf/
    # cp XEN3_DOM0 ELHAYM_DOM0
    # emacs ELHAYM_DOM0
    # config ELHAYM_DOM0
    # cd ../compile/ELHAYM_DOM0
    # make depend && make
    # cp netbsd /netbsd-dom0
    

    Pour avoir testé, il est sensiblement plus difficile de partir de la configuraiton personnalisée (dérivée de GENERIC) pour la rendre compatible avec Xen, que de partir de la configuration Xen pour l’adapter à la machine…

    Quelques défauts remarquables du noyau compatible Xen par rapport à un noyau NetBSD normal :
    • pas de SMP ;
    • pas de changement de fréquence SpeedStep du processeur (corrigé dans NetBSD-current, il me semble) ;
    • l’ACPI a quelques soucis chez moi (le poweroff ne fonctionne pas et la machine s’arrête simplement, sans s’éteindre)…
  3. Une fois le nouveau noyau fraîchement compilé, il suffit d’ajouter la ligne correspondante dans le boot.cfg. Autrefois, il fallait passer par Grub, à la place du bootloader NetBSD ; ce n’est plus le cas aujourd’hui, et tant mieux ! Les manips sont d’autant plus simple.

    À noter que la ligne à ajouter ne lance pas le nouveau noyau directement ; elle doit lancer l’hyperviseur, en lui indiquant le chemin du dom0. Quelque chose comme ceci (dans boot.cfg, il faut placer les instructions sur une seule ligne en les séparant par des points-virgules, bien sûr) :

    load /netbsd-dom0 console=pc
    multiboot /usr/pkg/xen3-kernel/xen.gz dom0_mem=max:1536M
    

    Limiter la mémoire pouvant être allouée au dom0 permet d’être sûr de pouvoir créer les domU quand on en a besoin. Je n’ai pas trouvé d’autre moyen de réduire la mémoire consommée par un domaine.

  4. Vient ensuite le domU, la configuration duquel est contrôlée par un fichier, que j’ai, pour ma part, simplement emprunté aux exemples fournis avec le paquetage :

    # cp /usr/pkg/share/examples/xen/netbsd1 /usr/pkg/etc/xen/test
    

    Le fichier est commenté, et c’est plutôt explicite. Cependant, quand on débute dans ce vaste monde, comme moi, ce n’est pas forcément évident pour autant…

    1. Tout d’abord, ça paraît stupide, mais domU, au départ, correspond à une machine vierge, sans système d’exploitation. Cela ne sert donc pas à grand chose de charger un noyau domU (par exemple le noyau XEN3_DOMU standard de NetBSD) vu qu’il ne trouvera rien sur le disque… Il faut en fait charger un noyau d’installation (INSTALL_XEN3_DOMU), qui contient un ramdisk avec sysinst.

      #kernel = '/netbsd-domu'
      kernel = '/netbsd-install-domu'
      
    2. La seconde variable d’intérêt s’appelle disk. Elle décrit les disques virtuels disponibles à l’intérieur du domU. Créer un disque virtuel vivant dans un fichier est le plus simple :

      vif = ['file:/var/xen/images/test.img,0x01,w']
      

      Pour l’installation du domU (avec le noyau d’installation), il ne faut pas non plus oublier de configurer le disque virtuel correspondant au CD-ROM contenant les sets d’installation NetBSD (les gros fichiers .tgz contenant l’userland précompilé) :

      vif = ['file:/var/xen/images/test.img,0x01,w',
             'file:/var/xen/images/install.iso,0x02,r']
      

      L’ISO se télécharge simplement sur un miroir NetBSD, dans le dossier iso/ (séparé des autres dossiers de fichiers d’installation).

      Ces disques apparaîtront dans le domU NetBSD sous la forme de périphériques xbd (le premier étant xbd0, le second xbd1, etc.). D’autres systèmes interprètent cela différemment.ε

      ε : Sous Linux, le second paramètre de la description du disque virtuel spécifie le périphérique à créer dans le domU.

      Le seul point délicat sur lequel je suis tombé est que pour utiliser un fichier en guise de disque virtuel (file:), il faut absolument que ce fichier ne soit pas creux (sparse), sans quoi vnd(4), le périphérique virtuel permettant de monter un fichier comme un disque, est incapable de fonctionner correctement.

      En pratique, cela signifie utiliser dd count=N plutôt que dd seek=N pour créer le fichier.

    3. Enfin, la configuration du réseau mérite un point à elle toute seule. Le principe est que Xen expose deux interfaces réseau (type Ethernet) connectées : une du côté domU (qui s’appelle xennet sous NetBSD) et une du côté dom0 (xvif sous NetBSD).

      Ce qui est fait de ces interfaces est laissé libre à l’administrateur, et est typiquement contrôlé par des scripts. Il y a deux scripts fournis avec le paquetage, vif-bridge et vif-ip, permettant respectivement d’obtenir un pont (bridge(4)) ou une simple IP statique privée associée au domU.ζ

      Pour ma part, j’ai choisi la seconde solution, car elle me convient mieux. Je n’ai pas besoin d’accéder à Internet depuis mon domU, et si cela devait se présenter, je pense que j’essaierai de mettre en place de la redirection, et du NAT. Pour utiliser par défaut vif-ip, il suffit de modifier le fichier de configuration de xend (/usr/pkg/etc/xen/xend-config.sxp, syntaxe s-exp, d’où le nom) :

      (vif-script vif-ip)
      

      Avec une telle configuration, la ligne de configuration du vif ressemble à :

      vif = ['ip=192.168.127.2 netmask 255.255.255.0']
      

      La valeur du sous-paramètre ip est simplement passée à ifconfig(8) pour configurer l’interface xvif (côté dom0). Le script ajoute automatiquement le sous-réseau mentionné (ici 192.168.127/24) dans la table de routage afin de diriger le trafic en direction de celui-ci à travers la bonne interface xvif. Cela signifie, en bref, qu’il suffit de donner une adresse IP appartenant au même sous-réseau à l’interface xennet, du côté du domU, et tout marche comme si les deux domaines étaient sur un même réseau !

      ζ : Le script proposé dans le howto NetBSD/xen est périmé. Celui fourni avec le paquetage semble très bien marcher.

    Une fois le fichier de configuration écrit, il suffit de faire appel à xm create, et tak.

  5. L’installation se passe comme une installation classique, par CD-ROM (à part que le périphérique CD est ici xbd1). Une fois terminée, il suffit de changer de noyau (dans le fichier de configuration du domU), et hop ! Une fois les briques de base en place, on peut passer à l’installation des paquetages que l’on veut.

    Pour ça, j’ai utilisé la solution cheap : un chroot(8) dans le vnd(4), en réutilisant les paquetages précompilés par pkgsrc pour ma NetBSD principale (avec un petit mount_union(8)).

    # vnconfig vnd0 /var/xen/images/test.img
    # mount /dev/vnd0a /mnt/
    # mkdir /mnt/usr/pkgsrc/
    # mount -t union -o -b /usr/pkgsrc/ /mnt/usr/pkgsrc/
    # chroot /mnt/ /bin/sh
    # export PKG_PATH=/usr/pkgsrc/packages/All/
    # pkg_add ...
    

    Et voilà ! Un joli environnement de test. :)

Impressions

Avec tout ça, j’ai un truc qui tourne. Je peux même tout simplement copier le disque en l’état, pour produire des environnements de test en masse.

Ceci dit, même si je considère Xen comme une technologie « cool », tout n’est pas rose. Au-delà des défauts que possède la version dom0 de NetBSD (principalement l’absence de SMP), j’ai eu du mal à trouver de la doc pertinente, et c’est un gros problème pour l’adoption de Xen par un public moins gurutique, à mon avis.

Cela mis à part, comme dit plus haut, je trouve, à titre personnel, que Xen avec NetBSD, c’est plutôt facile à mettre en place une fois que l’on a compris le truc ; et même si la doc comporte quelques erreurs, à l’heure actuelle, cela donne une bonne base sur laquelle partir.

Je ne sais pas ce qu’il en est des distributions Linux. J’entends souvent les gens opposer KVM à Xen. Je ne connais pas KVM donc je ne saurais pas trop dire ce qu’il en est. Mais du coup, il semblerait que l’adoption de Xen dans le monde Linux soit moindre… dommage ? Je ne saurais pas dire.

[ tag:blog.huoc.org,2009:posts/35 ]
Voir les commentaires · Commenter

/\ \/

Le code et ses raisons : goto en C

#. Par rz0 dans Le code et ses raisons. Publié le 10.09 2009 à 13:06. 14 commentaires.
<. Conventions : le retour
>. Le code et ses raisons : typedef en C
c débutants goto idiomes pratique programmation

Tak tak, aujourd’hui Ours répond ! Quelqu’un m’a demandé hier pourquoi mon code contenait autant d’instructions goto, la malfamée. Comme c’est une question qui revient souvent, je me suis dit que cela pouvait intéresser notre audience moins expérimentée en C. Here comes!

Ahem, commençons. goto, c’est un truc un peu vite fait obscur du C, un peu comme les trucs Caml obscurs de bluestorm mais quand même pas au même niveau de louchitude. goto, ça déchaîne les jeunes comme les vieux, tout ça au final pour une pauvre instruction qui sautille. Bref, goto ça transporte le flot d’exécution d’un endroit à un autre d’une fonction. Ça, c’est la description objective, notre ptit Get the Facts avant d’embrailler sur la vraie question : pourquoi diable utilise-t-on goto ? Je ne pourrai pas répondre pour « on » mais je peux répondre pour moi !

goto est avant tout l’instrument du pauvre. Voilà qui est dit. La raison principale derrière l’utilisation de goto est que celui-ci permet d’émuler des flots d’exécution atypiques et indisponibles en C. On parle souvent de gestion des erreurs, d’exceptions du pauvre ; ce n’en est qu’un cas particulier. L’idée générale est celle que je viens d’énoncer : goto permet d’écrire des programmes C avec des structures non présentes directement dans le langage.

Lol le C, c’est pour les ptits joueurs, ya pas d’exceptions

Quelques exemples maintenant, avec tout d’abord le grand classique : la sortie de boucles imbriquées et la gestion des erreurs. Pourquoi ai-je donc regroupé ces deux cas qui apparaissent distincts ? Car ils modèlent tous deux un type de flot permis par les exceptions dans les langages qui en possèdent : on dispose de contextes imbriqués desquels on veut se défaire en un saut.α

α : Notez que je n’ai pas dit que les exceptions pouvaient être émulées par des goto, juste qu’il y a des ressemblances dans le flot obtenu.

Jetons un œil à deux bouts de code. La sortie de bloc, tout d’abord :

{
    while (a) {
        while (b) {
            if (c)
                goto end;
            ...
        }
    }
end:
    ...
}

Et la gestion des erreurs, dans sa forme modelant une pile :

{
    if ((a = f(...)) == NULL)
        goto bada;
    if ((b = g(...)) == NULL)
        goto badb;
    ...
    return ...;
badb:
    antif(a);
bada:
    return ...;
}

Le premier exemple ne devrait demander aucun commentaire. Le second n’est pas aussi évident : il n’exhibe pas clairement l’idée de contextes imbriqués. Celle-ci est en vérité masquée par le court-circuitage introduit par les sauts. Nous aurions pu écrire :

{
    if ((a = f(...)) != NULL) {
        if ((b = g(...)) != NULL) {
            ...
        } else {
            antif(a);
            goto bada;
        }
    } else {
    bada:
        return ...;
    }
    return ...;
}

OK, le tout s’est sensiblement enlaidi, et je vous rassure, personne ne l’écrit ainsi (car en plus d’être laid, on effectue beaucoup plus de sauts en cas d’erreur). L’avantage de cette forme, cependant, est qu’elle modélise beaucoup plus précisément le comportement d’une exception dans un langage de plus haut niveau : chaque bloc if correspondrait à un bloc try (ou équivalent) avec une clause de finalisation (contenue dans la partie else). Lorsqu’une condition exceptionnelle se produit, les clauses de finalisation sont déroulées les unes après les autres, dans l’ordre d’empilement des contextes (représentés ici par les blocs).

À part remarquer que la gestion des erreurs, cas normal d’utilisation des exceptions, n’est pas celui qui possède la correspondance la plus naturelle avec l’utilisation de goto, nous n’avons plus grand chose à dire ici. Passons donc à…

Plus tiré par les cheveux (encore) : appels récursifs terminaux

Si vous jetez un œil aux codes sources que j’écris, il arrive que j’utilise des goto à la place de boucles classiques. Et oui, sacrilège § L’explication ? ’Tention, c’est tordu : cela modélise des appels récursifs terminaux simples.β

L’exemple typique est celui d’une fonction :

{
    T x;

re:
    if (...)
        ...
    if (...)
        ...
    if (...) {
        x = f(x, ...);
        goto re;
    }
}

Alors oui, on pourrait bien sûr écrire tout ça sous forme d’une boucle ou carrément utiliser l’appel récursif et prier pour que le compilateur C optimise.γ

C’est en vérité une question d’emphase, et ces questions là sont profondément subjectives. Une boucle mettrait moins l’accent sur l’action et davantage sur sa répétition. Dans le cas où le cas récursif est marginal, la construction s’en retrouve relativement maladroite : on a alors le choix entre une condition de boucle obscure et redondante, des marqueurs booléens dans tous les sens, ou des instructions break et continue judicieusement placées. En choisissant cette dernière solution, qui semble la moins contraignante, on se retrouve avec un code dans ce genre-là :

{
    T x;

    for (;;) {
        if (...)
            ...
        if (...)
            ...
        if (...) {
            x = f(x, ...);
            continue;
        }
        break;
    }
}

Mieux ? Je ne crois pas, mais je n’irai pas non plus cracher sur celui qui écrit cela. :) Tout cela au fond pour une question de…

β : Là aussi, soyons fous mais pas trop, on ne peut pas émuler la récursivité terminale dans le cas général avec goto.

γ : GCC le fait, m’enfin, d’autres compilateurs ne le font pas forcément, ce n’est pas une optimisation très orthodoxe en C comme elle peut l’être dans les langages fonctionnels, l’idiome étant en Cδ de tout convertir en style itératif.

δ : Mais rms a trop fait de Lisp, comme tout le monde sait. Lalala.

Lisibilité !

C’est là que les gens se fâchent, que les regards se clashent, que la violence se lâche, wash wash. Hum, les uns disent de ne pas utiliser goto, pour la clarté du code, et pour les mêmes raisons, d’autres l’utilisent ! C’est la vie. Après tout, il y bien des gens qui codent en style GNU en pensant se rendre lisibles. :)

La question de la lisibilité de goto tient principalement à sa nature la plus primaire : la possibilité de passer librement d’une portion de code à une autre. Les détracteurs de goto pointent du doigt les utilisations de l’instruction qui engendrent des transitions difficiles à suivre dans le flot. Ceux qui en défendent le gain visuel évoquent la possibilité offerte par goto de déporter des blocs de code d’un endroit à un autre. Cette astuce est principalement utilisée pour la gestion des erreurs et permet ainsi de dégager du traitement utile la gestion des cas de défaillance, rares en pratique et souvent triviaux (l’action à entreprendre étant, la plupart du temps, de faire remonter l’information telle qu’on la reçoit).

Pour conclure sur un dernier exemple, j’abuse souvent de cette propriété pour écrire des choses comme celles-ci :

{
    T *p;
    R *q;

    p = malloc(sizeof *p);
    q = malloc(sizeof *q);
    if (p == NULL || q == NULL)
        goto bad;

    ...
    return ...;

bad:
    free(q);
    free(p);
    return ...;
}

Mon style vous répugne ? N’hésitez pas à commenter. :)

Annexe : setjmp et ses copains

Si, de mon expérience, goto est généralement relativement bien reçu dans la communauté des programmeurs C dans son ensemble (excepté dans certains cercles, et dans l’enseignement, sans doute de peur de pervertir les étudiantsε), il en est autrement de setjmp, le « goto non local ».ζ

Il y a probablement plusieurs raisons à cela : la difficulté d’utilisation (cf. restrictions énoncée par la norme, dont la nécessité de déclarer toutes les variables locales manipulables après le saut volatile), mais aussi les divers problèmes lié à la conservation et la restauration des états : il faut pouvoir replacer de manière sûre le contexte au point où il était au moment de l’appel à setjmp. Cela implique la libération de mémoire et l’appel de destructeurs pour les données allouées dans l’intervalle, mais également des notions de plus bas niveau telles que la gestion des signaux (cf. sigsetjmp(3)). En plus de ça, setjmp avec toute cette complication engendre des performances moisies, en général.

setjmp est toutefois utilisé, parfois, pour implémenter des mécanismes d’exceptions, notamment dans la kazlibη (pour le C) et diverses implémentations portables de langages dynamiques (l’interpréteur interactif d’OCaml, si je me rappelle bien, et d’autres dont je ne me rappelle plus…).

ε : Il paraît que ce sont les éléments perturbateurs tels que moi qui tentent leurs petits camarades avec le côté obscur. :)

ζ : Notez que GCC possède également des goto non locaux et indirects grâce aux pointeurs d’étiquettes. Je ne m’étendrai pas à ce sujet (car je connais mal le GNU C). Plus d'informations dans le manuel de GCC.

η : Je ne vois plus dans Google la page Web correspondant. Le projet est peut-être bien décédé…

Annexe : omg l’optimisation qu’elle est bonne

On entend dire parfois que goto c’est pas bon pour les performances. Bon alors, dans l’absolu, hum, en fait il n’y a pas d’absolu. On pourrait dire que remplacer des structures de contrôle classiques par goto enlève de l’information sémantique, et bon, dans l’absolu, c’est vrai. La question est de savoir si cette information sémantique est pertinente d’une part, et si elle est utilisée, d’autre part. Question pertinence, c’est, là encore, un peu subjectif : est-ce que tel ou tel goto remplace simplement une combinaison intelligente de structures de contrôle ou ne reflète-t-il un concept absent du C ? C’est davantage une question philosophique, donc humaine, que d’importance à la machine… car en vérité, la plupart des compilateurs modernes ne tiennent absolument pas compte de ce genre de choses, et le langage intermédiaire employé n’emploie que des sauts. Ce sont ensuite les algorithmes d’analyse de flot (brrr, théorie des graphes) qui ont à leur charge de différencier les blocs et autres constructions utiles au compilateur.

[ tag:blog.huoc.org,2009:posts/29 ]
Voir les commentaires · Commenter

/\

Conventions : le retour

#. Par rz0 dans Le code et ses raisons. Publié le 25.08 2009 à 14:46. Aucun commentaire.
>. Le code et ses raisons : goto en C
c débutants idiomes pratique programmation

Comme il ne coûte pas cher de déterrer des brouillons presques prêts pour en faire des articles, voici le retour de l’article sur les conventions que j’avais commencé à écrire, et duquel avait été tiré mon petit poème.

Ceci est donc un article orienté débutants. « Woah, rz0 il fait des articles de vulgarisation maintenant. » Et ouais les cocos, ce sont des choses qui arrivent, dans la vie. Parfois, en se levant le matin, et qu’il est déjà midi, on n’a pas envie de plancher tout de suite sur la refactorisation qui a échoué la veille. Bref, trève de bavardage.

Les conventions, c’est quoi ? Le principe est simple, c’est celui de la bonne ptite ouvrière : faire la même chose toujours de la même manière. Oubliez vos pulsions d’artiste et embrassez pleinement la voie de la machine (l’autre voie de la machine, pour ceux qui sont déjà en prépa) !

Et je ne parle pas ici de ne pas nommer une variable prgcnt et une autre MyFckngVisitorCount juste à côté, ça c’est la différence entre lEs GenS Ki ÉkrIv KoM sA et le monde civilisé.

Non, l’idée est que si une variable assure un certain rôle plusieurs fois, alors elle devrait avoir le même nom à chaque fois. Un peu comme les païens du C++ appellent toujours (sont toujours forcés d’appeler) le pointeur sur l’objet manipulé this. Si vous écrivez une bibliothèque de tableaux dynamiques, vous pouvez par exemple décider que n correspond toujours au nombre d’éléments du tableau actuellement considéré.

Heureusement, Le Créateur a pensé à vous, et les anciens vous ont légué une pelleté de noms prêts à l’emploi. La bonne nouvelle, c’est que vous pouvez les utiliser. La mauvaise, c’est que vous devez les utiliser. (Et c’est ici qu’intervenaient mes talents poétiques.)

Car pour la descendance, Dieu l’ordre dicta :
Pour les itérations, les compteurs i, j, k ;
n, m : des quantités ;
p, q devront pointer ;
c pour les caractères ;
s et t pour les chaînes.
Mais ni o ni l, les suppôts du binaire,
Pour que le programmeur toujours lise sans peine.

Mais il est souvent tout aussi utile de définir vos propres conventions, pour vos objets, vos types, vos règles du jeu. Ou simplement de préciser les règles traditionnelles (comme nous l’avons vu juste au-dessus avec n).

Toujours utiliser les mêmes noms pour les variables de mêmes rôles vous évitera d’avoir à commenter leur déclaration avec des banalités du genre :

int lol;      /* Nombre de cailloux dans la marre. */

Remarquez aussi, souvent, vous pourrez choisir des noms conventionnels courts. Si après avoir lu mon chant à la gloire du Grand, vous écrivez toujours indiceCaillou au lieu d’i dans votre module de gestion des cailloux dans les marres, ma foi, je ne peux rien pour vous (sinon prier pour votre âme, pour lui éviter le knout).

Un point de débat possible sur la question, cependant, est le suivant : faut-il privilégier la précision ou la convention ? (Et non, indiceCaillou ne relève pas de la précision à mes yeux.)

Imaginez, vous utilisez p pour toutes vos variables de type Meuh, en entrée des fonctions de votre module. Cela tombe bien, elles ne prennent chacun qu’un seul objet à examiner ! Parfois, en interne, vous avez besoin d’un second pointeur, que vous appelez naturellement q ; tout va bien, vous avez spécialisé les conventions existantes, parfait. Maintenant, imaginez que vous vouliez ajouter une fonction copier qui copie la valeur d’un Meuh dans un autre. Elle aura deux paramètres. Doit-on les appeler dest et src, pour la précision ? Ou p et q pour suivre la convention ? Cela nous amène au point suivant.

Tout comme il est important de se fixer des conventions pour les noms de variables, il est souvent très utile de vous définir des ordres de passage d’arguments respectés par toutes les interfaces de votre unité. Ce sont des règles du type « l’objet ayant le rôle X précède toujours cela de rôle Y ».

L’exemple typique ici est la fonction de copie. La destination doit-elle précéder ou suivre la source ? Il n’y a pas de réponse universelle, et chacun a ses préférences, mais il est important de toujours utiliser la même stratégie de classement de vos paramètres.

Pour revenir au problème précédent, s’il semble d’abord aberrant de vouloir appeler ses paramètres p et q, si l’on considère que l’ordre de passage est bien établi, alors l’usage ne fait aucun doute : p est toujours le premier pointeur et q le second, l’ordre des arguments se chargeant de donner son sens à cette position. Les deux alternatives sont donc viables ; à vous de choisir celle qui colle le plus à votre style.

Notez enfin que le point de vue est souvent influencé (un peu ou beaucoup) par le paradigme dominant. Si, disons, vous réfléchissez en orienté objet et avez codifié que le paramètre numéro un était l’objet sur lequel on opérait, alors le fait de toujours nommer cet objet de la même façon prend tout son sens. Si au contraire, vous adoptez une vision plus procédurale, un autre schéma de noms vous sera peut-être plus naturel…

Et voilà pour les conventions ! Notez que j’ai fait exprès d’éluder le sujet des idiomes, sujet fort proche. La raison ? La flemme, bien sûr. Mais pourquoi pas un jour prochain, ou un autre encore. Qui sait, tant qu’il y a du désir, et des gens pour me lire. Lalala.

[ tag:blog.huoc.org,2009:posts/25 ]
Aucun commentaire · Commenter

>> Page : 0 1