0
Les langages de programmation actuellement utilisés par l'ensemble des développeurs, malgré l'amélioration des paradigmes et autres sucre syntaxiques , restent basés sur un certain nombres de primitives très restreint : Affectation d'une valeur en mémoire, calcul arithmétique sur celle-ci, branchement conditionnel.Muni de ces axiomes, le code exécuté, même dans des langages fonctionnels, objet et autres, travaille sur les données qu'il possède à l'instant T, manipulant des contenus d'adresses mémoire : il s'agit d'une sémantique opérationnelle.
Les projets informatique grossissent, le nombre de bug et de coût de maintien avec, et c'est guère l'amélioration de sucre syntaxique ou paradigmatique des langages utilisés dans l'industrie, qui permettra de tenir la barre le plus longtemps possible.
Il y a deux ans, votre serviteur avait traduit les interview de Jarold Lanier et Victoria Livschitz , tous deux (à l'époque) chercheurs dans les laboratoires de SUN, critiquant le manque de sémantique des langages.
Jarold Lanier, en particulier, relevait judicieusement que les langages que l'on connait, ne sont que la virtualisation des câbles connectant les blocs logiques d'ordinateur primitifs comme l'ENIAC. Celui-ci préconisait une approche plus basé sur la reconnaissance de forme.
Victoria Livschitz répond à son collègue en déclarant que c'est le manque d'intuivité de la sémantique des langages de programmation modernes qui implique la difficulté de maîtrise des gros projets informatique.
Elle cite aussi l'impossibilité pour une brique logiciel de s'adapter à un nouvel environnement, et partant, de la difficulté de créer des architectures pérennes.
Le problème réside donc dans la sémantique des langages, et moins dans les méthodes de génie logiciel qui sont elles très étudiées.
Il n'y a qu'à voir tout l'engouement des journalistes et architectes pour les technologies SOA ( Source Wikipedia : Service_Oriented_Architecture) et autres méta-trucs.
Le talon d'achille de cette approche est que l'on retombe toujours sur du code classique : ce ne sont que des générateurs de code qu'il faut ensuite faire vivre, maintenir... quand le code généré a la qualité d'être lisible, voire non bogué..
Après quelques réflexions, principalement réalisées dans le cadre du projet Isaac, dont le langage offre des possibilités inédites et réjouissantes, j'en suis venu à vous proposer quelques mécanismes, issu de mon expérience personnelles et surtout professionnelle.
Je me contente de proposer quelques primitives de plus, principalement pensé pour des langages objets, mais probablement adaptable pour d'autres langages. Je ne suis pas à l'abri d'avoir réinventé la poudre, conçue ici et là. Certains mécanismes ne sont que la généralisation de concepts vu ailleurs.
Les utilisateurs de langage orientés aspect reconnaitront peut être des choses faisable avec cette approche, mais un des principes majeur de cette réflexion est d'essayer de rendre la programmation plus intuitive. Cela implique souvent des choix qui peuvent paraitre arbitraire : utiliser une syntaxe de type SQL dans un langage objet est sémantiquement équivalent à d'autres syntaxes plus adapté à la logique objet, mais surement moins intuitives, le SQL se rapprochant du langage naturel.
Il faut donc bien avoir à l'esprit que concevoir des briques pour langages de plus haut niveau est autant de l'informatique théorique que de la psychologie humaine : il faut rapprocher la sémantique d'un langage de l'imagination humaine et de sa psychologie.
Je précise un vocabulaire lisaacien que j'utiliserai dans le texte qui suit :
- Un objet ne doit pas être vu comme un objet d'un langage à classe. Il est cloné et non instancié, il peut changer dynamiquement de parents à l'exécution
- Un slot, est un élément d'un objet. C'est soit une donnée, soit une méthode. En Lisaac, une donnée peut devenir du code, et vice-versa.
- Un block est une fonction, ou peut être encore vu comme une liste d'instructions à évaluation retardée. Un block peut prendre plusieurs valeurs en argument et renvoyer plusieurs autres valeurs. Un bloc s'exécute lorsqu'on appel le message value. Bien évidemment, un block peut être promené un peu partout et exécuté comme on veut. Il peut aussi être transformé en donné (du type de données qu'il est censé renvoyer).
- la syntaxe
- unevariable : TYPE := valeur_d_init [ code;]; représente une définition de la variable de unevariable de type TYPE, qui a l'initialisation vaudra valeur_d_init, et dont on définira un contrat, dont le code est contenu entre les crochet.Ce code sera exécuté à chaque accès (en lecture ou écriture) sur la variable. C'est une sorte de primitive de type aspect. Nous pensons rajouter une primitive dans le compilateur permettant de n'exécuter du code que lorsque l'on écrit ou lit la variable, ce qui permettra d'éviter des traitement inutile. Pour plus d'information, se référer au manuel d'utilisateur de Lisaac
Commençons par le premier que j'ai pompé chez TrollTech. Vous avez sûrement deviné : le système de Slots/Signaux
Ce concept consiste à permettre à des élément d'interface graphique d'être connecté à la méthode d'un objet quelconque, afin de lui envoyer un message lorsqu'un évènement quelconque relatif audit élément survient.
L'intérêt est qu'on a pas à se trimbaler des références d'objet dans les diverses parties du code. On défini une connexion à l'initialisation, et il suffit ensuite d'écrire le code associé.
Ce modèle pose quelques problèmes dans une logique d'objet à classe, mais beaucoup moins dans une logique d'objet à prototype : en effet, un prototype n'est pas une définition formelle d'un objet qui deviendra vivant à l'instanciation, il est d'ors et déjà vivant dès que le programme début son exécution. Il n'y a donc pas de problème, à définir une connexion entre deux objets qui ne s'échangeront jamais leur référence.
Bien utilisé, cela permet une séparation propre du code selon un pattern MVC, en permettant en plus de threader tous les traitements.
Lisaac étant un langage permettant de rendre et prendre en argument des n-uplet typés, on pourrait imaginer des connecter des slot typés.
Par exemple, si l'on connecte un champ éditable d'interface utilisateur, et que l'on connecte l'évènement "modification du texte" à un slot, on pourra utiliser un signal qui renvoi la chaine du champ texte nouvellement modifié. Le slot connecté doit donc prendre une chaîne en argument.
C'est le compilateur qui fera la vérification de cohérence de type à la compilation.
evenement_changement_texte.connect block_a_executer;Le mapping directionnel de données avec filtre
Très souvent dans les logiciels que j'ai eu à écrire, j'ai passé pas mal de temps à écrire de la glue entre éléments d'interfaces, voires objets, et organiser un système de rafraichissement, permettant de synchroniser deux couches d'objet, et autres variables. J'imagine que je suis loin d'être le seul...
Tous ces problèmes reviennent en réalité à définir une équation d'équivalence entre données.
Soit à poser axiomatiquement :
Donnée2 = fonction(Donnée1)
Ce mécanisme consiste donc à définir qu'un objet est toujours égal à un autre, via une relation fonctionnelle.
Cela implique, que toutes modifications de Donnée1 implique un traitement du type Donnée2 = fonction(Donnée1) mettant à jour le contenu de Donnée2, où qu'elle se trouve.
Cela peut être implémenté pratiquement grâce à des langages orientés aspect, comme AspectJ par exemple, où, en Lisaac, grâce à la possibilité de définir un contrat sur un slot, donc une variable.
liststr : LIST[STRING] := NULL [ Self := otherobject.unechaine.split ";"];Ici, on défini que liststr est par définition otherobject.unechaine splité pour le ";". On est obligé (de par la syntaxe actuelle de Lisaac) de définir une valeur "de départ" pour ce slot.
Ce genre de détail à un intérêt majeur dans des interfaces de logiciel gestion, ou il suffira de définir des filtres entre les objets de la vue et du modèle, la vue se mettant à jour toute seule.
Une réflexivité sur les arbres
Lorsqu'on code, on travaille beaucoup sur les arbres, et force est de constater que c'est souvent galère.
Caml nous apporte deux outils fabuleux pour simplifier la difficulté : les types somme et le filtrage de type.
On peut définir un arbre très simplement :
type arbre = Feuille of string | Noeud of arbre;
Un filtrage de type nous aide ensuite à trier
let rec affichearbre = function
Feuille(s) -> print s
|
Noeud (n)-> affichearbre n;;
L'ajout de cette primitive éprouvée dans pas mal de langage aiderait souvent. On pourrait ensuite greffer d'autres mécanismes, existantes dans des logiciels comme CodeWorker.
La gestion du temps
En sémantique opérationnelle, le code exécuté à un instant T travail sur l'état de la mémoire à ce même instant T. Ceci implique que l'on doit en permanence mettre de côté des données, gérer des dépendances diverses et variées afin de simuler une possibilité qui nous semble naturelle pour l'esprit humain : recueillir des informations à partir des évènement de la vie passé, dont on se souvient
Un langage de très haut niveau possède une sémantique permettant de gérer la dimension supplémentaire qu'est le temps. On devrait pouvoir demander au programme de nous donner des informations calculée précédemment, mais sans nécessiter de les mettre de côté.
Prenons un exemple : on doit réaliser un traitement sur des milliers de fichiers XML (une petite manipulation de l'arbre). Ce traitement a la particularité de nécessiter deux passes, car le deuxième traitement, nécessite que le premier soit terminé sur tous les fichiers.
En effet, le document global est éclaté en milliers de fichiers qui s'appellent les uns les autres, on cherche à poser des liens hypertextes dans cette documentation à partir des données contenus dans les documents, puis à vérifier que ces liens sont bijectifs (un lien doit toujours renvoyer vers un document qui permet de revenir au document précédent).
On utilise un objet que l'on va créer pour chaque fichier et que le GC détruira pour nous.
Dans la sémantique du langage, on devrait pouvoir exprimer qu'une information donnée se trouve parmi tous les objets exécutés à tel endroit du code.
Lorsqu'un block de code contenant une boucle a exécuté un traitement sur tous les fichiers, on devrait pouvoir interroger ce block de code sur les liens qui ont été posés.
Pour illustrer certains autres mécanismes, je propose de jouer avec du pseudo-code autour de l'écriture de certaines partie d'un petit jeu de sous-marin : BlueWar est un vieux jeu des années 80 qui consistait à manoeuvrer un sous-marin pour descendre tous les bateaux ennemis qui trainait dans les parages. Ce jeu est sorti sur Amiga, Thomson TO8/TO9, et surement d'autres plateforme !
On disposait de plusieurs vues comme l'écran de commande, avec périscope, carburant disponible, lance torpille ou l'indispensable carte dynamique sur laquelle on voyait se déplacer tous les bateaux.
Vous pourrez avoir une idée de ce qu'était la première version de ce jeu sur Thomson TO8, en regardant quelques screenshot : http://www.logicielsmoto.com/viewsoftware.php?softid=57
Selon un modèle Vue-Agents, ce logiciel comporte
Une vue périscopique avec
- Profondeur
- Charge des batteries, niveau de réservoir
- niveau d'oxygène
- 2 indicateurs de torpille
- Direction
- Voyant avarie
Une vue radar
Une carte marine dynamique
On défini le terrain de jeu :
Agents/sma.li (le sytème multi agent dans son ensemble)
- mysubmarine : SELFSUBMARINE;
- vaisseau_ami : LIST[VAISSEAUAMI];
- vaisseauennemi : LIST[VAISSEAUENNEMI];
Puis le sous-marin que l'on va commander :
Agents/selfsubmarine.li :
Section Private
- time : TIME;
- profondeur : INTEGER := 0;
- charge_batterie : INTEGER;
- niveau_reservoir : INTEGER := 0 [Self := Self.modulo 300];
- moteur_electrique : BOOLEAN;
- vect_direction : VECT2D [(Self.module_zero > 1).if { warning "Attention, le module du vecteur direction n'est pas à 1 !\n"};];
- torpille_prete_à_lancer1, torpille_prete_à_lancer2,avarie : BOOLEAN;
- niveau_oxygene := 0 [Self := Self.modulo 100];
- vitesse : INTEGER;
- position : VECT2D [TIME.each_second {Self := Self+vitesse*vect_direction};]; // On colle une équation liant position, vitesse et direction
- pression_trop_importante : BOOLEAN := FALSE;
- periscope : BOOLEAN; // périscope utilisable en surface;
Equation d'Etat
Il arrive très souvent que l'on ait besoin de programmer une machine à état plus ou moins complexe. Ce qui est particulièrement pénible en procédurale, l'est beaucoup moins avec un langage objet. Mais cet exercice reste encore souvent fastidieux. On aimerait pouvoir définir un ensemble d'équations d'état, qui, du moment qu'elle sont cohérentes entre elle (et c'est là qu'on déplacera la difficulté), permet de définir un agent animé et autonome.
J'utilise mon exemple de jeu BlueWar, pour décrire, par l'exemple, ce que pourrai être une définition d'état.
La syntaxe, inspirée du langage Lisaac est totalement fictive et n'est même pas une proposition, prière de ne donc pas la commenter.
On définit une section dans le code où l'on va "poser" les "équations"
Section StateLa section State ne définie pas des fonctions destinées à être appelées, mais des fonctions d'état fonctionnant à la manière d'un flip-flop : une fois enclenchée un état A, seul l'enclenchement d'un autre état B, avec une équation spécifiant que l'enclenchement B annule l'état de A, arrête cet état A.
Un état n'est donc pas un morceau de code exécuté à un instant T, mais la manière d'être de l'objet/agent, durable.
Je vois donc deux type de code définissable dans un agent :
- Soit l'exécution d'une équation d'état
- Soit l'exécution d'un block selon une équation de temps (que ce soit une fois par seconde ou plus souvent/irrégulièrement). Ce block sera donc réexécuté selon l'équation temporelle. Elle définira l'animation de l'agent.
On a les propriétés suivante pour un état
- Un état est paralellisable
- Un état est démarrable ou stopable arbitrairement.
- Un état est démarrable ou stopable par une équation d'état.
On définie ici une équation entre 3 états :
- gestionprofondeur : STATE_EQUATION := plonger ^ remonter ^ maintenirprofondeur;On a ici défini que le sous-marin soit remonte à la surface, plonge, ou se maintient à la même profondeur (ou exclusif).
- init <- // appelé à la création
(
TIME.each_minute {
moteur_electrique.if {
charge_batterie := charge_batterie - 2;
} else { // La combustion consomme de l'oxygène
niveau_oxygene := niveau_oxygene - 2;
niveau_oxygene := niveau_reservoir - 2;
};
};
);
Définition des 3 états :
- plonge <-
(
TIME.each_second { profondeur := profondeur - 5;};
{profondeur > 300) => {pression_trop_importante := TRUE; plonge.off;}; // le sous-marin implose à cause de la trop grande pression
);
- remonte <-
(
// Code a exécuter à intervals réguliers
TIME.each_second { profondeur := profondeur + 5} until {profondeur := 0};
// Equation d'état
(profondeur = 0) => {
maintient_profondeur.on; // on démarre l'état "maintient_profondeur"
remonte.off; // on stop l'état "remonte"
};
);
La logique d'une définition d'état n'étant pas la même, il faut bien voir qu'on a ici deux définition parallèle, et non successives : toutes les secondes la profondeur du sous-marin va diminuer et l'équation sera régulièrement testé.
L'idéal serait que le compilateur réécrive le code de sorte que l'équation d'état soit exécuté à la suite du block
{profondeur := 0}, car cela évite des tests régulier.
- maintien_profondeur <-
(
// A la surface on recharge les réserves en oxygène.
(profondeur = 0).if {
niveau_oxygene := 100;
périscope := TRUE;
};
);
On revient à du code plus classique où l'on définie les slots d'un objet :
Section Public
- set_direction nb : INTEGER <- // converti orientation horloge -> vecteur 2D
(
vect_direction.x := (nb*pi/6).cos;
vect_direction.y := (nb*pi/6).sin;
);
- lance_une_torpille <-
(
// ...
);
On défini 3 objets, sans leur code :
Agents/vaisseau.li
Agents/vaisseauEnnemi.li hérite de vaisseau
Agents/VaisseauAmi.li hérite de vaisseau
Le radar
View/Radar.li
On définie ici un mapping de données directionnelles, avec filtre :
radar : LIST[VECT2D] [select position from sma.vaisseau_ennemi, sma.vaisseau_ami where (position.module sma.mysubmarine) < 100;];
On notera qu'on a ici utilisé une requête OQL afin sélectionner les vaisseaux se trouvant dans un disque de 100 de rayon autour de mysubmarine
View/Carte.li
- points_vaisseaux : LIST[VECT2D] [Self := select position from sma.vaisseau_ennemi, sma.vaisseau_ami;];
L'ensemble des primitives que je propose ici sont assez difficile à exécuter pour un interpréteur et encore plus à compiler, en particulier pour les sémantiques de gestion d'état et d'interrogation de données sur traitement effectués dans le passé.
Cela nécessite une nouvelle race de compilateur doté d'une intelligence artificielle, capable de détecter pas mal d'incohérence dans le code et ainsi de prévenir des code qui s'emballent, boucles, et autres comportement ingérable.
Cela nécessitera aussi d'autres méta-machins, pardon méthode de modélisation et gestion de projets informatique.
L'avenir nous dira si tout cela est possible.
> Lire le journal (91 commentaires, moyenne: 1,6).
Vous avez demandé le commentaire #893329.



Les trous de lIsaac
Curieux de tous les langages qui augmentent le degré d'abstraction, qui facilitent la relecture et qui limitent le nombre d'erreurs de manière auto, j'ai décidé de jeter un petit coup d'oeil à Lisaac. Visiblement destiné à de l'embarqué, on est très loin de ce que propose un ruby pour de la gestion, il possède nombre de qualité qui réduit fortement le coût de codage par rapport à du C. Il intégre la notion d'agent, cela me parait efffectivement une bonne piste pour cette cible.
Etant néophyte sur ce lisaac, je me suis limité à parcourir les doc et faire quelques tests typiques. Voici mes premières remarques sur les limite du pouvoir d'expression de lisaac actuellement :
- Pas d'élément de rupture de séquences (type return, exit ,...). S'il est facile de substituer ces éléments par des tests, il n'en est pas de même pour la lecture. Cela me parait un manque, il serait souhaitable que le compilo prenne en charge cette transformation.
- Je n'ai pas vu de notion d'exception (pas à la java, mais plutot à la ADA) ce qui permettrait de remonter correctement les erreurs provenant des couches basses sans ajouter une nuée de tests partout. J'en profite pour dire que gestion d'exception ne doit pas être simplement une gestion d'erreur comme cela a été implementé dans de nombreux langages. La programmation par exception releve plus du concept évènementiel et entrerait totalament dans la philosophie de lissaac de ce point de vue.
La gestion d'exception permettrait également de mieux traiter les retours d'erreurs. Un exemple, une fonction C embeddée dans le core provoque une erreur, j'obtiens actuellement un magnifique "core dump" lisaac pas très corporate ;-); Une exception me permettrait de trapper l'erreur et de prendre la décision adéquat. C'est une chose indispensable si le logiciel doit s'auto-géré.
Une dernière remarque sur l'intégration d'exception aux langages, le traitement d'exception doit faire partie intégrante de la structure de bloc du langage pour être lisible (les try catch et autres syntaxes de ce genre sont à prohiber..., l'approche ADA est certainement la meilleure sur ce point)
- Dans la même veine, les pre et post conditions peuvent être une réponse élégante aux conditions "exceptionnelles" (à rapprocher de la gestion d'exception). Dans ce contexte, elle ne doivent pas seulement être cantonnée au debug. Je verrais bien des pre/pro conditions + et - liée au débug ou toujours activée qui déclancherait des exceptions trappable.
Ainsi, si j'ai la fonction log avec comme pré-condition x > 0, Je ne doit pas avoir à faire de tests pour une valeur saisie < 0, une exception doit pouvoir être générée et trappé au moins pour relancer le processus.
Ces quelques manques liés à la jeunesse du langage devrait permettre faire augmenter le niveau d'abstraction de lisaac.
Sinon Bravo, c'est un bon début et la gestion des agents me semble une piste prometteuse.
[^]Re: Les trous de lIsaac
Pour le 1er point, je crois qu'il y a une bonne raison que j'ai oublié :)
pour le 2), cela revient à la gestion signal/slot plus généraliste non ? Concernant le fait de rattraper les erreurs d'une fonction C embarqué, tu en demandes beaucoup. Je ne suis pas sur qu'un setjump/longjump avec récupération des signaux soit suffisant pour rattraper du C compilé.
Je crois que le 3 est faisable à travers des contacts, non ?
[^]Re: Les trous de lIsaac
Pour le premier point, c'est tout simplement parce que le concepteur de Lisaac, considère que les mots clés du type break, etc... permettent à de très mauvaises habitudes de se développer.
Le return car cela permet de faire quitter la fonction en plein de milieu de celle-ci, ce qui est très mauvais. Le break, car tu sort d'une boucle n'importe comment.
J'évite d'utiliser ce genre d'ecueuil dans mon code, au début ça a été dur, mais au final, ça me simplifie la vie au debugging (moins de surprise).
[^]Re: Les trous de lIsaac
"Le return car cela permet de faire quitter la fonction en plein de milieu de celle-ci, ce qui est très mauvais."
Pourquoi ?
"Le break, car tu sort d'une boucle n'importe comment."
Cad ?
MonoFrance
[^]Re: Les trous de lIsaac
Effectivement, il est toujours possible de coder sans rupture de sequence, toutefois cela est souvent au détriment de la clarté de l'algorithme.
Un deuxieme argument souvent présenté est la difficulté de prouver le code contenant des ruptures... Il s'agit d'un probleme d'analyse de code donc hors de notre contexte...
Sachant qu'un code est ecrit une fois et re-lu N-fois durant la vie du programme en maintenance, je privilégie la facilité de relecture.
Une succession de tests complexes est plus lisible avec des break qu'avec une succession de clause else qui perdent leur sens au bout de 3 ou 4 conditions...
Un langage se voulant de haut niveau comme Lisaac me semble devoir faciliter la vie du codeur pour exprimer le plus simplement possible l'algorithme; le reste de la tuyauterie doit être le domaine de l'optimisation et du controle du compilo.
Pour qu'un langage soit attractif face aux langages installés, il est nécessaire qu'il puisse offrir les mêmes facilités, un pouvoir d'expression plus important (le minimum de mots nécessaires pour décrire un concept) et être lisible (un algo doit pouvoir s'écrire le plus naturellement possible).
Il me parait curieux, d'apporter des contextes très avancés comme ceux présentés dans cet article en laissant à coté des lourdeurs syntaxiques qui rebuteront nombre de programmeurs.
[^]Re: Les trous de lIsaac
J'y connais pas grand chose : En quoi les exceptions de ADA diffèrent de celles du c# ou du java ?
J'ai lu hier cet article très intéressant [1] qui démonte les exceptions de ces deux derniers langages, est-ce que c'est le même problème avec ADA ?
[1] http://blogs.msdn.com/oldnewthing/archive/2005/01/14/352949.(...) sur lequel je suis tombé en lisant ce très intéressant (bis :) article mais un peu long [2]
[2] http://www.joelonsoftware.com/articles/Wrong.html
[^]Re: Les trous de lIsaac
Je ne vois pas en quoi cet article démonte les exceptions C# et Java, ni en quoi il démonte le besoin d'exceptions tout court.
Gérer des exceptions n'empêche absolument pas de gérer des erreurs ou des débranchements possibles à coup de if.
En particulier son It's really hard to write good exception-based code since you have to check every single line of code (indeed, every sub-expression) and think about what exceptions it might raise and how your code will react to it., je ne vois pas en quoi c'est spécifique aux exceptions.
Et au passage, Java force à catcher les exceptions qui sont susceptibles d'être levées (et signale qui à quelle ligne peut en provoquer une), donc le think about en est grandement facilité.
[^]Re: Les trous de lIsaac
Pour répondre à ta première phrase, de mon point de vue, l'article montre qu'il est plus difficile de faire un bon code avec exception qu'un bon code avec une gestion des erreurs à la main (maintenant je dis pas que l'un est mieux que l'autre, mais que les exceptions c'est galère :)
Maintenant, pour avoir coder en java en essayant d'exploiter les exceptions à fond, c'est vrai que ça devient *très* rapidement un truc ultra lourd avec des millions de try/catch de throw, et qu'il est toujours complexe de tout bien prendre en compte. Dès qu'on appelle une fonction il faut pas simplement catcher les exceptions (ce que Java t'oblige à faire de toute façon) mais aussi *réfléchir* à comment les gérer, et pire, comment *bien* les gérer.
Pour moi tout ça montre que les exceptions, c'est pas la panacée pour gérer les erreurs, et donc je me demandais comment était utilisée les exceptions dans ADA :)
[^]Re: Les trous de lIsaac
Il s'agit encore d'un problème de lisibilité et non de défense d'ADA.
En ADA chaque bloc possède implicitement un section exception qui fait partie de la structure syntaxique. Il est donc aisé de mettre en oeuvre une exception; la programmation par exception est alors réalisable pour exprimer clairement un algorithme et ses conditions d'arrêts qui peuvent être analysés à differents niveaux y compris au niveau le plus haut pour re-initialisé un système par exemple.
Begin
...
Exception:
excep1 => traitement1()
End
Les langages plus récents ont cantonnés les exceptions à de la gestion d'erreurs tout en voulant adjoindre une mécanique de remontée d'informations. Ceci est louable mais alourdi considérablement la syntaxe au point de ne pouvoir l'utiliser pour autre chose de la gestion d'erreurs. Il suffit d'imbriquer 2 ou 3 blocs d'exception en Java pour se rendre compte du probleme.
Dans un système temps réel, les conditions d'arrêt sont multiples et souvent provenant de cause hors logiciel, il est plus simple de lâcher une exception et de la trapper au niveau adéquat pour reprendre le traitement sans devoir gérer la pile d'erreurs en cascade en retour de chaque fonction.
[^]Re: Les trous de lIsaac
Une manière élégante et très puissante de gérer les execptions est d'utiliser des monades. Le concept brièvement c'est :
Une fontion f d'un type A dans un type B qui peut générer une exception de type C peut être vu comme une fonction sans exception du type A dans le type "une valeur B ou une exception C". Mettons qu'on ai deux fonctions comme ca: f et g que l'on veut composer ou executer séquentiellement, l'idée c'est d'avoir un opérateur (disons >>= ) pour lier les deux (f >>= g) de telle sorte que si f renvoie une valeur B on passe a g et si elle renvoie l'exception, on n'execute pas g et on renvoie directement C.
Est ce que les block de Lisaac peuent être vu comme des fonctions anonymes ? Si oui Lisaac devrait le supporter et donc avoir des exceptions. Je suis tombé sur un article intéressant décrivant des monades pour Ruby [1], comme quoi les monades sa sert partout.
[1] http://moonbase.rydia.net/mental/writings/programming/monads(...)
[^]Re: Les trous de lIsaac
Tout d'abord merci d'avoir regardé de près le Lisaac.
L'absence de Break ou de Return ou d'exception ont été un choix délicat :
1) Ce sont des ruptures de contexte, donc des "goto" cachés. La garantie de linéarité
du code est quelque chose d'important au niveau génie logiciel.
(Il aide aussi à l'analyse de flot et la recherche de preuve sur le code)
2) Lorsque nous avons un langage ayant la programmation par contrat, cette linéarité
est aussi importante : nous garantissons l'exécution des pré, du code, puis des posts
sans se poser de question d'interruption. Il s'est avéré qu'il était bien agréable de ne pas
avoir besoin d'analyser tout le code d'un fonction pour ajouter une action à la fin de
celle-ci.
C'est le choix de Bertrand Meyer, multe fois argumenté dans son livre et directement mise en oeuvre dans son langage Eiffel.
3) la sortie par break ou par return dans une imbrication importante de code est un signe
d'un code mal fragmenté (découpage insuffisant en fonctions). Cette absence incite le
programmeur à fragmenter plus son code pour le rendre plus lisible.
J'ai comme vous beaucoup programmé avec la possibilité de return ou de break,
mais après une période de sevrage (En Eiffel, puis en Lisaac), j'apprécie cette absence...
4) Le choix d'un langage minimaliste : ce qui n'est pas indispensable n'est pas ajouté.
C'est un choix de goût, ou plutôt de philosophie. Le Lisaac a une grammaire plus petite
que SmallTalk et n'a rien à voir avec celle de Java ou C#...
Nous ajoutons que si cela est indispensable ! Plus le langage est petit et uniforme dans sa
façon de penser, moins il est difficile à comprendre ou à apprendre...
Evidemment, pour être d'accord avec cela, il ne faut pas avoir la philosophie suivante :
"Un langage est facile a comprendre ou apprendre si il ressemble à ce que je connais !"
(Qui est la philosophie de Java ou C# avec leurs syntaxes proche du C.)
Ici, ma philosophie fonctionne mieux si on considère que la personne ne connais rien.
(c'est dans cette même idée que SmallTalk, avec Squeak, est considéré comme un langage destiné aux enfants)
Pour les exceptions, la programmation par contrat recouvre en partie, et de manière plus
élégante, l'utilisation des exceptions.
Mais, je te l'accord, ce n'est qu'en partie...
L'utilisation des Blocks à évaluation retardé ou "closure" (introduit en Lisp) recouvre encore une autre partie de l'utilisation des exceptions.
Il reste peut être encore des trous dans notre approche, qu'il va falloir analyser avec
beaucoup de précaution pour ne pas faire redondance et ne pas inclure une brique qui
va à l'encontre du génie logiciel.
L'approche des exceptions en Java n'est clairement pas la bonne, et nous avons l'air
d'être d'accord sur ce point, il faut donc y réfléchir... (pourquoi voir plutôt vers ADA)
Ce sont des débats assez délicats comme : "langage typé vs langage non typé" ou encore
le choix d'avoir une syntaxe drastique sur les majuscules et minuscules...
Cela reste des choses argumentables , mais c'est aussi des trous sans fin à discussions philosophique. Nous avons une philosophie bien précise autour du Lisaac, nous essayons
de nous en tenir pour réaliser un langage racé de qualité dans sa catégorie, et non un
langage four tout n'ayant plus d'âme. :-)
[^]Gestion d'erreur lisaac
Je suis totalement en phase avec cette dernière approche, c'est une des raisons pour laquelle je m'interesse à lisaac.
Pour les break/return ..., je ne suis pas certain que l'on puisse l'assimiler à un goto qui est explicitement une mauvaise pratique, je l'assimilerai plus à une simplification de conditionnelles imbriquées, pratique de mon point de vue saine, d'ailleurs die_with_code() existe et effectue cette action bien au delà du bloc puisqu'il s'agit de la sortie intégrale du programme.
Mais il semble s'agir effectivement d'un choix conceptuel déjà tranché, aussi je ne reviendrais plus dessus.
Je souhaiterai par contre revenir sur la gestion d'erreur de manière plus générale. Contexte dans lequel les exceptions fournissent une solution élégante à la problématique :
1. La gestion d'erreur ne devrait pas être attaché uniquement à une phase debug, nombre de circonstances provoquent des erreurs, souvent lié au logiciel ou matériel sous-jacent, donc difficilement contrôlable et anticipable.
Prenons l'exemple de lisaac qui s'appuie sur des lib C, en cas d'erreur de la lib, lisaac devrait fournir un mécanisme permettant de trapper l'erreur et prendre la décision adéquat. Quelques types par defaut existent généralement (erreur de contrainte, Div/0, systeme, ...) .
Un de mes premiers tests lisaac a été l'utilisation de la ligne de commande, étonnant non ;-) ...; en itérant un cran trop loin (trop classique...), j'ai eu le droit à un magnifique core dump... . Je m'attendais plutot à erreur de contrainte ou erreur de Fctn sous-jacentes beaucoup plus explicite.
2. Le traitement d'erreur n'est pas forcement réalisé au même niveau que la détection et de survenance de l'évènement.. Il est donc nécessaire de pouvoir remonté la pile des appels.
Dans un système complexe, toutes les interactions ne sont pas forcement déterministe (en particulier quand on s'interface avec des éléments externes, il est alors pertinent d'avoir un mécanisme générique et simple d'emploi sans devoir spécifier à tous les étages les types d'erreurs attendus ... ;-)
3. Afin d'être traiter convenablement, l'information de contexte d'erreur doit exister au moment du traitement.
4. Une exception doit pouvoir remonter la pile d'exécution, jusqu'à son traitement éventuel.Si ce n'est le cas, le programme s'arrête avec sa pile d'exécution classique.
Dans ce contexte, Lisaac possède quelques embryons intéressants :
1. La programmation par contrat couvre effectivement bien la partie déclenchement d'exception à condition que l'on puisse ajouter un contrat sur affection de valeur et non uniquement en pre et post condition. Dans ce cas, de manière déclarative, il est possible d'exprimer une contrainte qui sera sujette à erreur et donc déclenchement d'exception.
Un exemple classique simpliste, la lecture d'un capteur doit retourné une valeur entre 1 et 10, Lors de la lecture, une valeur de 0 ou 11 déclenche une exception qui interrompra les appels en cours, jusqu'au niveau du controleur du système qui trappera l'exception pour re-initialiser convenable le capteur.
Dans ce cas, en pseudo algo, cela peut être ecrit :
...
Valeur = lecture()
si Valeur <1 ou Valeur > 10 alors
raise Hors_limite;
...
Je préfere nettement, la vision déclarative qui sépare nettement les cas d'erreurs ou exceptionnels de l'algo nominale sans pour autant terminer l'application.
Header :
Check Valeur < 1 ou Valeur > 10, sinon exception Hors_limite
Code :
...
Valeur = lecture()
...
2. La propagation et la capture de l'exception reste à définir. Le contexte de remontée ne doit pas créer de nouveaux objets (difficile en cas de memoire pleine...) mais devrait plutot s'appuyer sur un contexte statique réservé à cet usage.
3. Dans une vision volontairement unifiée, les exceptions pourraient apporter à lisaac :
- un mécanisme générique de remontée d'erreur :
- émis par les contrats, pouvant être utilisé en debug mais également en production.
- émis volontairement par le programmeur
- être le support générique de terminaison de bloc, méthode ou programme.
Sur les exceptions ADA (dans le même esprit que cellede PL/1).
La structure de l'exception ADA est a la fois :
- intrégré dans la structure syntaxique (fait partie intégrante du bloc)
- simple d'usage
- Le contexte transmis est statique, une exception levée et trappée doit être traité avant q'une autre puisse être levée. ceci évite les tentation de traitement complexe dans l'exception.....
pour aller plus loin dans ce sens, un commentaire sur le sujet :
http://www.les-ziboux.rasama.org/faq-ada-presentation.html#e(...)
[^]Re: Gestion d'erreur lisaac
Franchement je préfaire largement la gestion signal/slot.
Par contre, il faut faire attention aux contrats: ils sont censé être sans effet de bord !