afficher >><< masquer ]
SAMPI - Editeur structuré
1. Le Problème et la Proposition
2. Le Langage Primitif de Représentation Textuelle
2.1. Présentation de la Syntaxe Concrète
2.2. Notations
2.3. Exemple de structuration des données
2.4. Exemple de structuration des traitements
2.4.1. La pile
2.4.2. Les lecteurs-écrivains
2.4.3. La racine carrée
2.4.33.1. Construction progressive
2.4.33.2. La modification du programme
2.5. Exemple de structurations connexes
3. Le Langage Complété pour la Structuration des Textes
3.1. Présentation de la Syntaxe Complétée
3.2. Etude quantitative de l'évolution des programmes
3.3. L'édition syntaxique
3.4. étude de cas : le langage LTR3 et l'atelier ENTREPRISE
4. L'Enrichissement du Langage par de Nouveaux Concepts
4.1. Présentation de la Syntaxe Abstraite
4.2. Les difficultés
4.3. Compléter la Syntaxe
5. La Formalisation des Solutions Techniques
5.1. L'évaluation fonctionnelle
5.2. La structuration par les objets
5.3. Modèle sémantique comparé de l'évaluateur
5.4. Comparaison critique
5.5. Construction de la Syntaxe Abstraite
6. Les Comparaisons avec d'autres Approches
7. Les Perspectives
8. Les Editeurs
8.0. brisé sur la barrière de la complexité (une fois de plus)
8.1. L'éditeur ligne : Manuel de l'utilisateur
8.2. L'éditeur page : Guide de l'utilisateur
9. Les Aspects d'Implantation
9.1. Contexte d'évaluation
9.2. La Syntaxe Abstraite : Manuel du concepteur
9.3. L'éditeur page : Guide de l'implanteur
Références
Rubrique Perl-Javascript

La racine carrée

L'exemple peut être vu selon deux jours :

1. Construction progressive

2. La modification du programme

2. La modification du programme

Dans cette seconde partie, on s'intéresse à la modification du programme. La modification d'un programme se justifie pour deux raisons :

La question qu'on doit se poser est de savoir si la modification préserve la sémantique du programme. Dans le premier cas, il est à souhaiter que la réponse soit non ; dans le second cas, on aimerait répondre par l'affirmative, et si possible d'une façon automatique. L'automatisation d'un tel contrôle est un problème résolument complexe, elle n'est pas supportée par l'outil qu'on propose. Dans la suite, on montre certaines difficultés qu'elle pose – sur la très simple fonction de calcul d'une racine carrée – en présentant deux outils qui pourraient en faciliter la résolution.

2.1. position du problème

La fonction précédemment construite est :

(de sqrt (num)
   (prog (result)
         (setq result 1)
      LP (cond ((< (abs (- (* result result) num)) 0.001) (return result)))
         (setq result (/ (+ result (/ num result)) 2))
         (go LP)))

On aimerait éviter la redondance du calcul multiplicatif – multiplication ou division – dans le test et l'approximation de l'algorithme. Pour ce faire on effectue le regroupement en deux étapes.


(1)

remplacer (- (* result result) num)
par (- result (/ num result))

(2)

regrouper les deux termes (/ num result)
par l'utilisation d'une variable auxiliaire X

2.2. la modification locale

R. Dybvig et B. Smith présentent dans [DyS 85] un « éditeur sémantique ». L'éditeur travaille sur une forme "lispienne" du langage FP défini par J. Backus [Bac 78] :

L'outil permet la saisie d'un terme de FP, puis sa transformation, via les règles d'équivalence orientées en règles de réécriture, à sémantique constante.

Une des originalités de l'approche est de gérer la relation d'équivalence que définissent ces règles : un programme conserve dans sa Représentation Interne les classes d'équivalence des termes qui le composent. On a donc deux interactions possibles sous l'éditeur :

Pour éclairer le propos, on a par exemple la règle d'équivalence :

(construct (compose f g) (compose f h))
   ≡ (apply-to-all f (construct g h))

(construct, apply-to-all, compose sont des opérateurs du langage FP).

Le programme est alors représenté par le graphe :

congruence de programmes

La flèche représente l'instanciation classique d'un nœud d'opérateur par un terme tel qu'on le trouve dans un arbre syntaxique.
Le pointillé symbolise la relation d'équivalence entre les termes.

Un « chemin sélectionné » sera par exemple le sous-graphe (orienté) :

congruence de programmes

congruence de programmes

qui est un programme sémantiquement équivalent au sous-graphe dans la "racine" est apply-to-all.

L'exemple

On veut remplacer le test :
(< (abs (- (* result result) num)) 0.001)
par le test :
(< (abs (- result (/ num result))) 0.001)
dont on "remarque" qu'il a une sémantique voisine.

En adoptant les notations mathématiques plus concises et en élargissant le problème, on veut remplacer :
|r1×r2-n| < ε
par le test :
|r2-n/r1| < ε

On transforme le premier terme (entre crochets on indique quelles règles d'équivalence on utilise) :

 |r1×r2-n| < ε    
 |r1×((r1×r2)/r1-n/r1)| < ε [ x-y ≡ z×(x/z-y/z) ]
 |r1×((r1/r1)×r2-n/r1)| < ε [ (x×y)/z ≡ (x/z)×y ]
 |r1×(1×r2-n/r1)| < ε [ x/x ≡ 1 ]
 |r1×(r2-n/r1)| < ε [ 1×x ≡ x ]
 |r1|×|r2-n/r1| < ε [ |x×y| ≡ |x|×|y| ]
 |r2-n/r1| < ε/|r1| [ x×y<z ≡ y<z/x si x>0 ]

On s'arrête ici, parce qu'on ne peut plus transformer le terme dans la direction qu'on suit. Or celui-ci est intuitivement équivalent à :
|r2-n/r1| < ε

En effet dans le cadre qui nous intéresse la majoration est « paramétrée » par un ε assez petit, donc si l'on suppose que |r1| est minoré alors majorer par ε/|r1| ou majorer par ε est à peu près équivalent. Il faudrait ici appliquer une règle de réécriture dépendante du problème (« problem dependant ») : dans l'absolu elle serait sémantiquement fausse, mais pour ce qui nous concerne elle serait presque vraie.

Le cas pose problème : si l'on s'impose des transformations à sémantique constante, on est arrêté à cette étape ; si l'on s'autorise des approximations, la relation de congruence sur les termes devient obsolète.

2.3. la modification globale

R. Walter présente un autre éditeur sémantique, « l'Apprenti du Programmeur » [Wat 82] [Wat 86]. Comme tout bon Apprenti, celui-ci se charge des « basses besognes » en laissant la « partie noble » du travail à la charge du Programmeur.

Le système fonctionne à plusieurs niveaux :

Par nature, le système ne garantit aucune conservation de la sémantique du programme lors d'une transformation : l'Apprenti n'est pas là pour enseigner au Programmeur les bonnes règles de la programmation mais pour bénéficier de la connaissance experte de ce dernier. Sa tâche est plutôt de contrôler que le Programmeur n'introduit pas de contradictions notoires, et tout particulièrement pour ce qui concerne les variables :

L'exemple

Par une "transformation syntaxique", on a transformé le programme en :

(de sqrt (num)
   (prog (result)
         (setq result 1)
      LP (cond ((< (abs (- result (/ num result))) 0.001) (return result)))
         (setq result (/ (+ result (/ num result)) 2))
         (go LP)))

A l'étape suivante, on regroupe les deux divisions (/ num result) par l'emploi d'une variable locale ; cette seule formulation d'« expression de besoin » par l'utilisateur fournit alors le programme :

(de sqrt (num)
   (prog (result X)
         (setq result 1)
      LP (setq X (/ num result))
         (cond ((< (abs (- result X)) 0.001) (return result)))
         (setq result (/ (+ result X) 2))
         (go LP)))

Que garantit le système ?

Les deux derniers points sont deux aspects intéressants du système. Ils sont le fait de la Représentation Interne du programme par un graphe de flot de données : on n'a pas la vue séquentielle habituelle d'un langage de programmation qui rend très ardue la réalisation des vérifications présentées. Une limitation sévère du système en est la conséquence : à chaque "bout de programme algorithmiquement significatif" doit être associé un graphe de flot de données ; si ce "bout" est dans la bibliothèque des objets prédéfinis on réutilise le graphe associé ; autrement il faut le définir, ce qui alourdit beaucoup l'étape de programmation.

2.4. le graphe de flot de données

En s'affranchissant de la question des déclarations de variable, on veut résoudre le problème suivant :

On part d'un « texte troué » sur lequel on a reconnu qu'à deux points distincts on élaborait le même calcul :

texte troué

Les deux « trous » sont donc remplis par le même texte qui réalise le calcul.

On veut savoir si l'on peut remplacer ce texte par le suivant :

texte troué

Pour réaliser une telle transformation en toute quiétude il faut donc démontrer qu'à nul point du « texte troué » on n'affecte des objets susceptibles d'affecter l'élaboration du calcul : un problème non simple.

La représentation « textuelle » qu'on propose de sait pas répondre à la question. En revanche les graphes de flot de données semblent assez bien adaptés à la situation. L'exemple précédent se réalise par les considérations qui suivent.

La fonction sqrt, de paramètre num, n'affecte pas la valeur du paramètre : num est globalement constant dans le corps de la fonction.

flot de données

flot de données: zone de valeur pertinente et constante de num
val: valeur du paramètre effectif à l'appel de sqrt
res: valeur résultat de l'appel de sqrt

Le CORPS est construit avec le schéma prédéfini succ-approx :

flot de données

flot de données: zone de valeur pertinente et constante de result à l'initialisation ou à l'itération de la boucle
flot de données: zone d'intersection

Pour le regroupement des deux divisions, on observe les points suivants :
- elles sont syntaxiquement égales, préliminaire indispensable ;
- elles apparaissent dans les "boîtes" TST et APPROX ;
- elles utilisent les valeurs des variables num et result.

L'intersection des « zones de valeur pertinente et constante » des variables num et result fournit la nouvelle "boîte" :

flot de données

qui contient justement les "boîtes" TST et APPROX.

Il suffit alors de placer l'affectation de X en tête de cette "boîte" :

flot de données

flot de données: zone de valeur pertinente et constante de X
div: la division

(et on remplace TST et APPROX dans l'élaboration de la division par l'accès à la valeur de X).