Cours gratuits » Cours informatique » Cours programmation » Cours Modula

Cours Modula


La langue de programmation Modula-2 est un digne successeur de Pascal dans l'enseignement et l'application de la programmation informatique. Comme Pascal, Modula-2 fournit les concepts de programmation fondamentaux qui sont essentiels à l'enseignement initial de la programmation en tant que discipline logique et systématique. En outre, il prévoit la construction de programmes à grande échelle sous une forme modulaire et fournit également une capacité de base pour la programmation du système au niveau requis dans les systèmes de commande intégrés et les applications similaires. Pour ces raisons, sa gamme d'application dans le développement de logiciels pratiques dépasse de loin celle de Pascal.

Histoire du langage Modula

Le 6 novembre 1986, Maurice Wilkes écrivait à Niklaus Wirth pour proposer que le langage Modula-2 + soit révisé et standardisé en tant que successeur de Modula-2. Wirth a donné sa bénédiction à ce projet, et le comité Modula-3 est né.

Lors de la première réunion, le comité a décidé à l'unanimité d'être fidèle à l'esprit de Modula-2 en choisissant des caractéristiques simples, sûres et éprouvées plutôt que d'expérimenter avec nos propres idées non éprouvées. Nous avons constaté que l'unanimité était plus difficile à atteindre quand nous sommes arrivés aux détails.

Modula-3 prend en charge les interfaces, les objets, les génériques, les threads légers de contrôle, l'isolation de code dangereux, la récupération de place, les exceptions et le sous-typage. Certaines des fonctionnalités les plus problématiques de Modula-2 ont été supprimées, comme les enregistrements de variantes et le type de données numérique non signé intégré. Modula-3 est sensiblement plus simple que d'autres langues avec une puissance comparable.

Modula-3 est étroitement basé sur Modula-2 +, qui a été conçu au Centre de recherche sur les systèmes Digital Equipment Corporation et utilisé pour construire le système Topaz [McJones89, Rovner86]. Le design Modula-3 était un projet commun de Digital et Olivetti. La définition du langage a été publiée en août 1988 et immédiatement suivie des efforts de mise en œuvre dans les deux entreprises. En janvier 1989, le comité a révisé le libellé pour refléter les expériences de ces équipes de mise en œuvre. Quelques révisions finales ont été faites pour la publication de ce livre.

SRC Modula-3 est distribué par le DEC Systems Research Center sous licence libérale. La distribution inclut un compilateur pour Modula-3, la boîte à outils Modula-3 Abstract Syntax Tree développée chez Olivetti, et un système d'exécution avec des fichiers de configuration pour les postes de travail DEC, IBM, HP et Sun.

Perspectives du langage Modula

La plupart des systèmes de programmation actuels sont fabriqués dans la famille de langages BCPL, qui comprend B, Bliss et C. La beauté de ces langages est le coût modeste avec lequel ils ont pu faire un grand bond en avant depuis le langage d'assemblage. Pour les apprécier pleinement, vous devez tenir compte des contraintes d'ingénierie des machines dans les années 1960. Quel langage conçu dans les années 1980 a un compilateur qui tient dans quatre mille mots de 18 bits, comme le compilateur B de Ken Thompson pour le PDP-7? La plus réussie de ces langues était C, qui au début des années 1970 avait presque complètement déplacé le langage d'assemblage dans le système Unix.

Les langages de type BCPL sont faciles à mettre en œuvre efficacement pour la même raison qu'ils séduisent les programmeurs sceptiques en langage assembleur: ils présentent un modèle de programmation proche de la machine cible. Les pointeurs sont identifiés avec des tableaux et l'arithmétique des adresses est omniprésente. Malheureusement, ce modèle de programmation de bas niveau est intrinsèquement dangereux. Beaucoup d'erreurs sont aussi désastreuses qu'elles le seraient en langage machine. Le système de type est rare, et révèle assez de bizarreries de la machine cible que même les programmeurs expérimentés et disciplinés écrivent parfois du code non portable simplement par accident. Le langage le plus moderne de cette famille, C ++, a enrichi C en ajoutant des objets; mais il a également abandonné la meilleure vertu de C --- la simplicité --- sans soulager le pire inconvénient de C - son modèle de programmation de bas niveau.

À l'autre extrême se trouvent des langages comme Lisp, ML, Smalltalk et CLU, dont les modèles de programmation proviennent des mathématiques. Lisp est l'hybride du lambda-calcul et de la théorie d'une fonction d'appariement; ML provient de la théorie des types polymorphes; Smalltalk d'une théorie des objets et de l'héritage; CLU à partir d'une théorie des types de données abstraits. Ces langages ont de beaux modèles de programmation, mais ils ont tendance à être difficiles à mettre en œuvre efficacement, car le traitement uniforme des valeurs dans le modèle de programmation invite un système d'exécution dans lequel les valeurs sont uniformément représentées par des pointeurs. Si l'implémenteur ne prend pas de mesures pour l'éviter, une instruction aussi simple que n: = n + 1 peut nécessiter une allocation, une recherche de méthode, ou les deux. Les bonnes implémentations évitent la plupart des coûts, et les langages de cette famille ont été utilisés avec succès pour la programmation des systèmes. Mais leur disposition générale vers l'allocation de tas plutôt que l'allocation de pile reste, et ils ne sont pas devenus populaires avec des programmeurs de systèmes. Les systèmes d'exécution requis pour rendre ces langages efficaces les isolent souvent dans des environnements fermés qui ne peuvent pas accepter les programmes écrits dans d'autres langues. Si vous êtes un fan de ces langues, vous pouvez trouver Modula-3 trop pragmatique; mais lisez en tout cas, et donnez-nous une chance de montrer que les contraintes pragmatiques n'excluent pas les solutions attractives.

Entre les extrêmes de BCPL et Lisp est la famille de langues Algol, dont les représentants modernes comprennent Pascal, Ada, Modula-2 et Modula-3. Ces langages ont des modèles de programmation qui reflètent les contraintes d'ingénierie des machines à accès aléatoire mais dissimulent les détails d'une machine particulière. Ils abandonnent la beauté et la symétrie mathématique de la famille Lisp afin de rendre possibles des implémentations efficaces sans astuces spéciales; ils ont également des systèmes de type fort qui évitent la plupart des caractéristiques dangereuses et dépendantes de la machine de la famille BCPL.

Dans les années 1960, la tendance dans la famille Algol était orientée vers les caractéristiques de flux de contrôle et de structuration des données. Dans les années 1970, la tendance était à la dissimulation d'informations comme les interfaces, les types opaques et les génériques. Plus récemment, la famille Algol a eu tendance à adopter une sélection rigoureuse des techniques des familles Lisp et BCPL. Cette tendance est démontrée par Modula-3, Oberon et Cedar, pour nommer trois langues qui ont flotté des implémentations portables ces dernières années.

Modula-3, Oberon, et Cedar fournissent tous la collecte des ordures, auparavant considérée comme un luxe disponible uniquement dans les systèmes d'exécution fermés de la famille Lisp. Mais le monde commence à comprendre que la collecte des variables non-utilisées est le seul moyen d'atteindre un niveau de sécurité adéquat, et que les éboueurs modernes peuvent travailler dans des environnements d'exécution ouverts.

En même temps, ces trois langages permettent un petit nombre d'opérations dangereuses, dépendantes de la machine, du type habituellement associé à la famille BCPL. Dans Modula-3, les opérations dangereuses ne sont autorisées que dans les modules explicitement étiquetés comme dangereux. La combinaison de la récupération de place avec l'isolation explicite des fonctions non sécurisées produit un langage adapté à la programmation de systèmes entiers depuis les applications de plus haut niveau jusqu'aux pilotes de périphériques de plus bas niveau.

Caracteristiques du language Modula-3

1- Interfaces

L'une des fonctionnalités les plus réussies de Modula-2 est la mise à disposition d'interfaces explicites entre modules. Les interfaces sont conservées avec pratiquement aucun changement dans Modula-3. Une interface à un module est une collection de déclarations qui révèlent les parties publiques d'un module; les éléments du module qui ne sont pas déclarés dans l'interface sont privés. Un module importe les interfaces dont il dépend et exporte l'interface (ou, dans Modula-3, les interfaces) qu'il implémente.

Les interfaces rendent la compilation séparée sûre pour le type; mais cela leur fait une injustice de les regarder d'une manière si limitée. Les interfaces permettent de penser à de grands systèmes sans tenir tout le système dans votre tête à la fois.

Les programmeurs qui n'ont jamais utilisé les interfaces de type Modula ont tendance à les sous-estimer, en observant, par exemple, que tout ce qui peut être fait avec des interfaces peut également être fait avec des fichiers d'inclusion de style C. Cela manque le point: beaucoup de choses peuvent être faites avec des fichiers d'inclusion qui ne peuvent pas être faits avec des interfaces. Par exemple, la signification d'un fichier d'inclusion peut être modifiée en définissant des macros dans l'environnement dans lequel il est inclus. Inclure les fichiers tente les programmeurs dans des raccourcis à travers les frontières d'abstraction. Pour que les grands programmes soient bien structurés, vous avez besoin d'une puissance de volonté super-humaine ou d'un support linguistique approprié pour les interfaces.

2- Objets

Mieux nous comprenons nos programmes, plus grands sont les blocs de construction que nous utilisons pour les structurer. Après l'instruction est venu la déclaration, après la déclaration est venu la procédure, après la procédure est venue l'interface. La prochaine étape semble être le type abstrait.

Au niveau théorique, un type abstrait est un type défini par les spécifications de ses opérations et non par la représentation de ses données. Comme cela est réalisé dans les langages de programmation modernes, une valeur d'un type abstrait est représentée par un "objet" dont les opérations sont implémentées par une suite de valeurs de procédure appelées "méthodes" de l'objet. Un nouveau type d'objet peut être défini comme un sous-type d'un type existant, auquel cas le nouveau type a toutes les méthodes de l'ancien type, et éventuellement de nouvelles (héritage). Le nouveau type peut fournir de nouvelles implémentations pour les anciennes méthodes (overriding).

Les objets ont été inventés au milieu des années soixante par les concepteurs clairvoyants de Simula [Birtwistle]. Les objets dans Modula-3 ressemblent beaucoup à des objets dans Simula: ils sont toujours des références, ils ont à la fois des champs et des méthodes de données, et ils ont un héritage simple mais pas un héritage multiple.

De petits exemples sont souvent utilisés pour faire passer l'idée de base: le camion en tant que sous-type de véhicule; rectangle en tant que sous-type de polygone. Modula-3 vise à des systèmes plus grands qui illustrent comment les types d'objets fournissent une structure pour les grands programmes. Dans Modula-3, l'effort de conception principal est concentré sur la spécification des propriétés d'un seul type abstrait --- un flux de caractères, une fenêtre sur l'écran. Ensuite, des douzaines d'interfaces et de modules sont codés qui fournissent des sous-types utiles de l'abstraction centrale. Le type abstrait fournit le modèle pour toute une famille d'interfaces et de modules. Si l'abstraction centrale est bien conçue, des sous-types utiles peuvent être produits facilement, et le coût de conception original sera remboursé avec intérêt.

La combinaison des types d'objet avec les types opaques Modula-2 produit quelque chose de nouveau: le type partiellement opaque, où certains champs d'un objet sont visibles dans une portée et d'autres sont cachés. Parce que le comité n'avait aucune expérience avec les types partiellement opaques, la première version de Modula-3 les restreignait sévèrement; mais après une année d'expérience, il était clair qu'ils étaient une bonne chose, et le langage a été révisé pour supprimer les restrictions.

Il est possible d'utiliser des techniques orientées objet même dans des langages qui n'ont pas été conçus pour les prendre en charge, en allouant explicitement les enregistrements de données et les suites de méthodes. Cette approche fonctionne raisonnablement bien lorsqu'il n'y a pas de sous-types; Cependant, c'est grâce au sous-typage que les techniques orientées objet offrent le plus de poids. L'approche fonctionne mal lorsque le sous-typage est nécessaire: soit vous allouez les enregistrements de données pour les différentes parties de l'objet individuellement (ce qui est coûteux et fastidieux) ou vous devez compter sur des transferts de type non contrôlés, ce qui est dangereux. Quelle que soit l'approche adoptée, les relations du sous-type sont toutes dans la tête du programmeur: seulement avec un langage orienté objet, il est possible d'obtenir une vérification de type statique orientée objet.

3- Génériques

Un module générique est un modèle dans lequel certaines des interfaces importées sont considérées comme des paramètres formels, pour être liées à des interfaces réelles lorsque le générique est instancié. Par exemple, un module de table de hachage générique peut être instancié pour produire des tables d'entiers, des tables de chaînes de texte ou des tables de n'importe quel type. Les différentes instances génériques sont compilées indépendamment: le programme source est réutilisé, mais le code compilé sera généralement différent pour différentes instances.

Pour garder les génériques de Modula-3 simples, ils sont confinés au niveau du module: les procédures génériques et les types n'existent pas isolément, et les paramètres génériques doivent être des interfaces entières.

Dans le même esprit de simplicité, il n'y a pas de vérification distincte associée aux génériques. Les implémentations sont censées développer le générique et typecheck le résultat. L'alternative serait d'inventer un système de type polymorphe assez flexible pour exprimer les contraintes sur les interfaces de paramètres nécessaires à la compilation du corps générique. Ceci a été réalisé pour ML et CLU, mais il n'a pas encore été atteint de façon satisfaisante dans la famille des langues Algol, où les systèmes de types sont moins uniformes. (Les règles associées aux génériques Ada sont trop compliquées à notre goût.)

4- Threads

Diviser un calcul en processus concurrents (ou threads de contrôle) est une méthode fondamentale de séparation des préoccupations. Par exemple, supposons que vous programmiez un émulateur de terminal avec un curseur clignotant: la manière la plus satisfaisante de séparer le code clignotant du curseur du reste du programme consiste à en faire un fil séparé. Ou supposons que vous augmentez un programme avec un nouveau module qui communique sur un canal mis en mémoire tampon. Sans threads, le reste du programme sera bloqué à chaque fois que le nouveau module se bloque sur son buffer, et inversement, le nouveau module sera incapable de traiter le buffer à chaque fois qu'une autre partie du programme se bloque. Si cela est inacceptable (comme c'est presque toujours le cas), il n'y a aucun moyen d'ajouter le nouveau module sans trouver et modifier chaque déclaration du programme qui pourrait bloquer. Ces modifications détruisent la structure du programme en introduisant des dépendances indésirables entre ce qui serait autrement des modules indépendants.

Les dispositions pour les fils dans Modula-2 sont faibles, ce qui revient essentiellement à des coroutines. Les moniteurs de Hoare [Hoare] constituent une base plus solide pour la programmation simultanée. Des moniteurs ont été utilisés à Mesa, où ils ont bien fonctionné; sauf que l'exigence qu'une structure de données surveillée soit un module entier était fastidieuse. Par exemple, il est souvent utile qu'une structure de données surveillée soit un objet à la place d'un module. Mesa a assoupli cette exigence, a apporté un léger changement dans les détails de la sémantique de la primitive Signal de Hoare, et a présenté la primitive Broadcast comme une commodité [Lampson]. Les primitives Mesa ont été simplifiées dans la conception de Modula-2 +, et le résultat a été assez réussi pour être incorporé sans changements substantiels dans Modula-3.

Un package de threads est un outil avec un bord très net. Une erreur de programmation courante consiste à accéder à une variable partagée sans obtenir le verrou nécessaire. Cela introduit une condition de concurrence qui peut rester inactive pendant les tests et la frappe après l'envoi du programme. Le travail théorique sur l'algèbre de processus a fait espérer que le modèle de rendez-vous de la concurrence serait plus sûr que le modèle de mémoire partagée, mais l'expérience avec Ada, qui a adopté le rendezvous, donne au mieux un support équivoque à cet espoir. , et apparemment ils sont largement utilisés.

5- Sécurité

Une fonction de langage n'est pas sûre si son utilisation abusive peut corrompre le système d'exécution de sorte que l'exécution ultérieure du programme ne soit pas fidèle à la sémantique du langage. Un exemple d'une fonctionnalité dangereuse est l'assignation de tableau sans vérification de limites: si l'index est hors des limites, alors un emplacement arbitraire peut être clobé et l'espace d'adressage peut devenir fatalement corrompu. Une erreur dans un programme sécurisé peut provoquer l'annulation du calcul avec un message d'erreur d'exécution ou donner une mauvaise réponse, mais cela ne peut pas provoquer le crash du calcul dans un décombres de bits.

Les programmes sécurisés peuvent partager le même espace d'adressage, chacun protégé de la corruption par des erreurs dans les autres. Pour obtenir une protection similaire pour les programmes non sécurisés, vous devez les placer dans des espaces d'adressage distincts. À mesure que de grands espaces d'adressage deviennent disponibles et que les programmeurs les utilisent pour produire des applications étroitement couplées, la sécurité devient de plus en plus importante.

Malheureusement, il est généralement impossible de programmer les niveaux les plus bas d'un système en toute sécurité. Ni le compilateur, ni le système d'exécution ne peuvent vérifier la validité d'une adresse de bus pour un contrôleur d'E / S, et ils ne peuvent pas non plus limiter le chaos qui s'ensuit s'il n'est pas valide. Ceci présente le concepteur de langage avec un dilemme. S'il tient pour la sécurité, alors le code de bas niveau devra être programmé dans une autre langue. Mais s'il adopte des caractéristiques dangereuses, alors sa garantie de sécurité devient nulle .

6- Garbage collector

Une erreur d'exécution classique non sécurisée consiste à libérer une structure de données qui est toujours accessible par des références actives (ou "pointeurs flottants"). L'erreur plante une bombe à retardement qui explose plus tard, lorsque le stockage est réutilisé. Si par contre le programmeur ne libère pas les enregistrements devenus inaccessibles, le résultat sera une "fuite de stockage" et l'espace de calcul croîtra sans limite. Les problèmes dus aux pointeurs pendants et aux fuites de stockage ont tendance à persister longtemps après que d'autres erreurs ont été trouvées et supprimées. Le seul moyen sûr d'éviter ces problèmes est la libération automatique du stockage inaccessible ou de la récupération de place.

Modula-3 fournit donc des "références tracées", qui ressemblent aux pointeurs Modula-2, sauf que le stockage vers lequel ils pointent est conservé dans le "tas tracé" où il sera libéré automatiquement lorsque toutes les références auront disparu.

Un autre grand avantage de la collecte des variable perimés est qu'elle simplifie les interfaces. Sans le garbage collection, une interface doit spécifier si le client ou l'implémentation a la responsabilité de libérer chaque référence allouée, et les conditions dans lesquelles il est sûr de le faire. Cela peut submerger l'interface en complexité. Par exemple, Modula-3 prend en charge les chaînes de texte par une simple interface Texte requis, plutôt qu'avec un type intégré. Sans ramassage des ordures, cette approche ne serait pas aussi attrayante.

De nouveaux perfectionnements dans la collecte des déchets sont apparus continuellement depuis plus de vingt ans, mais il est encore difficile à mettre en œuvre efficacement. Pour de nombreux programmes, le temps de programmation économisé en simplifiant les interfaces et en éliminant les fuites de stockage et les pointeurs flottants fait de la collecte des ordures une bonne affaire, mais les niveaux les plus bas d'un système peuvent ne pas être en mesure de le faire. Par exemple, dans le système Topaz de SRC, la partie du système d'exploitation qui gère les fichiers et les processus lourds repose sur le garbage collection, mais le «nub» interne qui implémente la mémoire virtuelle et la commutation de contexte thread ne le fait pas. Essentiellement, tous les programmes d'application Topaz s'appuient sur la récupération de place.

Pour les programmes qui ne peuvent pas assurer la récupération de place, Modula-3 fournit un ensemble de types de référence qui ne sont pas tracés par le garbage collector. Dans la plupart des autres cas, les références tracées et non tracées se comportent de manière identique.

7- Exceptions

Une exception est une construction de contrôle qui sort de nombreuses étendues à la fois. La levée d'une exception ferme plusieurs fois les étendues actives jusqu'à ce qu'un gestionnaire soit trouvé pour l'exception et transfère le contrôle au gestionnaire. S'il n'y a pas de gestionnaire, le calcul se termine de manière dépendante du système --- par exemple, en entrant le débogueur.

Il existe de nombreux arguments pour et contre les exceptions, dont la plupart portent sur des questions de style et de goût peu concluantes. Un argument en leur faveur qui a le poids de l'expérience est que les exceptions sont un bon moyen de gérer toute erreur d'exécution qui est généralement, mais pas nécessairement, fatale. Si des exceptions ne sont pas disponibles, chaque procédure susceptible de rencontrer une erreur d'exécution doit renvoyer un code supplémentaire à l'appelant pour identifier si une erreur s'est produite. Cela peut être maladroit et présente l'inconvénient pratique que même des programmeurs prudents peuvent par inadvertance omettre le test du code de retour d'erreur. La fréquence avec laquelle les codes d'erreur renvoyés sont ignorés est devenue une sorte de blague permanente dans le monde Unix / C. La levée d'une exception est plus robuste, car elle arrête le programme sauf s'il existe un gestionnaire explicite.

8- Système de type

Comme toutes les langues de la famille Algol, Modula-3 est fortement typé. L'idée de base de la typage fort est de partitionner l'espace de valeurs en types, de restreindre les variables pour contenir les valeurs d'un seul type et de restreindre les opérations à appliquer aux opérandes de types fixes. En réalité, le typage fort est rarement si simple. Par exemple, chacune des complications suivantes est présente dans au moins une langue de la famille Algol: une variable de type [0..9] peut être affectée en toute sécurité à un INTEGER, mais pas vice-versa (sous-typage). Les opérations comme valeur absolue peuvent s'appliquer à la fois aux REAL et aux INTEGER au lieu d'un seul type (surcharge). Les types de littéraux (par exemple, NIL) peuvent être ambigus. Le type d'une expression peut être déterminé par la manière dont elle est utilisée (typage de cible). Les discordances de type peuvent provoquer des conversions automatiques au lieu d'erreurs (comme lorsqu'un réel fractionnaire est arrondi lors de l'affectation à un entier).

Nous avons adopté plusieurs principes afin de rendre le système de type de Modula-3 aussi uniforme que possible. Premièrement, il n'y a pas de types ambigus ou de typage de cibles: le type de chaque expression est déterminé par ses sous-expressions, et non par son utilisation. Deuxièmement, il n'y a pas de conversions automatiques. Dans certains cas, la représentation d'une valeur change lorsqu'elle est affectée (par exemple, lors de l'affectation à un champ condensé d'un type d'enregistrement) mais la valeur abstraite elle-même est transférée sans modification. Troisièmement, les règles de compatibilité de type sont définies en termes de relation de sous-type unique. La relation sous-type est requise pour le traitement de