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
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.
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.
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)
Puis le sous-marin que l'on va commander :
Agents/selfsubmarine.li :
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"
La 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 :
On a ici défini que le sous-marin soit remonte à la surface, plonge, ou se maintient à la même profondeur (ou exclusif).
Définition des 3 états :
- 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
On revient à du code plus classique où l'on définie les slots d'un objet :
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 :
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
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.
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 #893385.



Hein ?
>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.
Tu mélanges le code produit et sémantique du langage lui-même.
Les processeurs actuels possèdent un jeu d'instruction impératif, appelé par ailleurs parfois opérations. C'est donc logique que le "code executé", comme tu dis le soit fait de façon opérationnelle puisque que le hardware sous-jacent ne sait rien faire d'autre.
Pour ce qui est des langages, les langages fonctionnels ne sont pas définis par une logique d'état mais par un notion de fonction ; c'est à dire, plus ou moins, par une correspondance entre les entrées et les sorties, peu importe la boite noire qui effectue cette transformation, l'important c'est la correspondance entrée-sortie (qui est déterministe).
On ne définit pas les langages fonctionnels en terme d'opération, d'étape mais en terme d'entrée et de sortie (un correspondance) ! Cela est du ressort, par exemple d'un sémantique dénotationnelle pas opérationnelle.
Le compilateur, d'un langage fonctionnel, quant à lui, effectue une correspondance entre le langage fonctionnel (à sem. dénotationnelle) et le langage machine (à sem. opérationnelle). Cette correspondance est, par ailleurs, une approximation (car pas de nombre infini, pas de précision infinie, ...).
[^]Re: Hein ?
Cela est du ressort, par exemple d'un sémantique dénotationnelle pas opérationnelle.
pas forcèment, on peut avoir une sémantique opérationnelle pour un langage fonctionel aussi (cf. la sémantique opérationnelle du lambda calcul).
[^]Re: Hein ?
Evidement qu'il est possible d'avoir un sémantique opérationnelle (pour un langage fonctionnelle (sinon, il serait vraiment pas facile d'écrire un compilateur qui est censé produire du code qui est orienté opérationnel).
Mais procéder de la sorte dénature la paradigme fonctionnel. Un peu comme écrire du OO/Fonctionnel/autres en C, c'est possible, mais c'est laid.
[^]Re: Hein ?
C'est tout à fait ce que je me disais. Il faisait (pas forcément volontairement, à chacun sa spécialité) l'omission des paradigmes fondamentaux de la prog fonctionnelle.
Le meilleur exemple étant sans doute quand il parle de ses "équations qui s'équilibrent". J'appelle ça littéralement un point fixe.
Il faut se dire quand même que si nombre de langages pèchent par absence de sémantique, on ne peut pas vraiment dire ça des langages fonctionnels (sauf de Caml, où la sémantique, c'est le code du compilo ^^).
C'est un bel exemple d'une branche de l'info où l'on passe plus de temps sans ordi qu'avec ordi, pour développer un concept, et il n'y a pas de place pour le debuggage à chaud (comptez-donc le temps écoulé pour l'aboutissement de Scheme R6RS)...
Bref, j'ai rien codé en Lissac encore, mais j'attendrai d'avoir un peu de formalisme derrière avant de m'y lancer (ne le prend pas mal, j'ai pas lu tes publis encore non plus)
J'aime la liberté.
J'aime BSD.